first commit

This commit is contained in:
2023-09-12 21:41:04 +02:00
commit 3361a7f053
13284 changed files with 2116755 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
.DS_Store
phpunit.xml
autoload.php
.idea

View File

@@ -0,0 +1,5 @@
language: php
php:
- 5.3
- 5.4

View File

@@ -0,0 +1,19 @@
Copyright (C) 2011 by TEQneers GmbH & Co. KG
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,188 @@
Git Streamwrapper for PHP
=========================
[![Build Status](https://secure.travis-ci.org/teqneers/PHP-Stream-Wrapper-for-Git.png)](http://travis-ci.org/teqneers/PHP-Stream-Wrapper-for-Git)
*Git Streamwrapper for PHP* is a PHP library that allows PHP code to interact with one or multiple Git repositories from within an application. The library consists of a Git repository abstraction that can be used to programatically access Git repositories and of a stream wrapper that can be hooked into the PHP stream infrastructure to allow the developer to use file and directory access functions directly on files in a Git repository. The library provides means to access status information on a Git repository, such as the log, the current repository status or commit information, as well.
The *Git Streamwrapper for PHP* core is a wrapper around the Git command line binary so it is required to have Git installed on the machine running the PHP code. ***Git Streamwrapper for PHP* does not include a Git protocol abstraction**, it relies on the Git command line binary for all its functionality.
**The code is currently running stable (see comments on Windows below) and should be API-stable. It's however not feature-complete - so please feel free to request features you require.**
Examples
--------
### Using the repository abstraction
```php
use TQ\Git\Cli\Binary;
use TQ\Git\Repository\Repository;
// open an already initialized repository
$git = Repository::open('/path/to/your/repository', new Binary('/usr/bin/git'));
// open repository and create path and init repository if necessary
$git = Repository::open('/path/to/your/repository', new Binary('/usr/bin/git'), 0755);
// get current branch
$branch = $git->getCurrentBranch();
// get status of working directory
$status = $git->getStatus();
// are there uncommitted changes in the staging area or in the working directory
$isDirty = $git->isDirty();
// retrieve the commit log limited to $limit entries skipping the first $skip
$log = $git->getLog($limit, $skip);
// retrieve the second to last commit
$commit = $git->showCommit('HEAD^');
// list the directory contents two commits before
$list = $git->listDirectory('.', 'HEAD^^');
// show contents of file $file at commit abcd123...
$contents = $git->showFile($file, 'abcd123');
// write a file and commit the changes
$commit = $git->writeFile('test.txt', 'Test', 'Added test.txt');
// remove multiple files
$commit = $git->removeFile('file_*', 'Removed all files not needed any more');
// rename a file
$commit = $c->renameFile('test.txt', 'test.txt-old', 'Made a backup copy');
// do some file operations and commit all changes at once
$result = $git->transactional(function(TQ\Git\Repository\Transaction $t) {
file_put_contents($t->getRepositoryPath().'/text1.txt', 'Test 1');
file_put_contents($t->getRepositoryPath().'/text2.txt', 'Test 2');
unlink($t->resolvePath('old.txt'));
rename($t->resolvePath('to_keep.txt'), $t->resolvePath('test3.txt'));
$t->setCommitMsg('Don\'t know what to write here');
// if we throw an execption from within the callback the changes are discarded
// throw new Exception('No we don\'t want to to these changes');
// note: the exception will be re-thrown by the repository so you have to catch
// the exception yourself outside the transactional scope.
});
```
### Using the streamwrapper
```php
use TQ\Git\Cli\Binary;
use TQ\Git\StreamWrapper\StreamWrapper;
// register the wrapper
StreamWrapper::register('git', new Binary('/usr/bin/git'));
// read the contents of a file
$content = file_get_contents('git:///path/to/your/repository/file_0.txt');
// show contents of a file at commit abcd123...
$content = file_get_contents('git:///path/to/your/repository/file_0.txt#abcd123');
// show contents of a file two commits before
$content = file_get_contents('git:///path/to/your/repository/file_0.txt#HEAD^^');
// show the directory information two commits before
$directory = file_get_contents('git:///path/to/your/repository/#HEAD^^');
// list directory contents two commits before
$dir = opendir('git:///path/to/your/repository/subdir#HEAD^^');
while ($f = readdir($dir)) {
echo $f.PHP_EOL;
}
closedir($dir);
// recursively traverse the repository two commits before
$dir = new RecursiveDirectoryIterator('git:///path/to/your/repository#HEAD^^');
$it = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::SELF_FIRST);
foreach ($it as $fileInfo) {
echo str_repeat(' ', $it->getDepth() * 3).$fileInfo->getFilename().PHP_EOL;
}
// retrieve the second to last commit
$commit = file_get_contents('git:///path/to/your/repository?commit&ref=HEAD^^');
// retrieve the commit log limited to 5entries skipping the first 2
$log = file_get_contents('git:///path/to/your/repository?log&limit=5&skip=2');
// remove a file - change is committed to the repository
unlink('git:///path/to/your/repository/file_to_delete.txt');
// rename a file - change is committed to the repository
rename('git:///path/to/your/repository/old.txt', 'git:///path/to/your/repository/new.txt');
// remove a directory - change is committed to the repository
rmdir('git:///path/to/your/repository/directory_to_delete');
// create a directory - change is committed to the repository
// this creates a .gitkeep file in new_directory because Git does not track directories
mkdir('git:///path/to/your/repository/new_directory');
// write to a file - change is committed to the repository when file is closed
$file = fopen('git:///path/to/your/repository/test.txt', 'w');
fwrite($file, 'Test');
fclose($file);
// support for stream context
$context = stream_context_create(array(
'git' => array(
'commitMsg' => 'Hello World',
'author' => 'Luke Skywalker <skywalker@deathstar.com>'
)
));
$file = fopen('git:///path/to/your/repository/test.txt', 'w', false, $context);
fwrite($file, 'Test');
fclose($file); // file gets committed with the preset commit message and author
// append to a file using file_put_contents using a custom author and commit message
$context = stream_context_create(array(
'git' => array(
'commitMsg' => 'Hello World',
'author' => 'Luke Skywalker <skywalker@deathstar.com>'
)
));
file_put_contents('git:///path/to/your/repository/test.txt', 'Test', FILE_APPEND, $context);
// unregister the wrapper if needed
StreamWrapper::unregister();
```
Requirements
------------
- PHP > 5.3.0
- Git installed on the machine running the PHP code
Run tests
---------
1. clone the repository
2. copy `phpunit.xml.dist` to `phpunit.xml`
3. adjust the `GIT_BINARY` constant in `phpunit.xml` to the path to your Git binary
4. run `phpunit` from within the cloned project folder
Please note that the library has been tested on a Mac OS X 10.7 with the bundled PHP 5.3.6 (git version 1.7.6), on several Ubuntu Linux installations and on Windows Vista running PHP 5.3.7 (1.7.6.msysgit.0). Due to currently unknown reasons the test run a bit unstable on Windows. All tests should be *green* but during cleanup there may be the possibility that some access restrictions randomly kick in and prevent the cleanup code from removing the test directories.
The unit test suite is continuously tested with [Travis CI](http://travis-ci.org/) on PHP 5.3 and 5.4 and its current status is: [![Build Status](https://secure.travis-ci.org/teqneers/PHP-Stream-Wrapper-for-Git.png)](http://travis-ci.org/teqneers/PHP-Stream-Wrapper-for-Git)
Contribute
----------
Please feel free to use the Git issue tracking to report back any problems or errors. You're encouraged to clone the repository and send pull requests if you'd like to contribute actively in developing the library.
License
-------
Copyright (C) 2011 by TEQneers GmbH & Co. KG
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1 @@
0.2.2

View File

@@ -0,0 +1,34 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
function tq_namespace_autoload($class)
{
if (strpos($class, 'TQ') === 0) {
$file = __DIR__.'/src/'.str_replace('\\', '/', $class) . '.php';
if (file_exists($file) && is_readable($file)) {
require_once $file;
}
}
}
spl_autoload_register('tq_namespace_autoload');

View File

@@ -0,0 +1,23 @@
{
"name": "teqneers/PHP-Stream-Wrapper-for-Git",
"description": "Git Streamwrapper for PHP",
"keywords": ["git", "streamwrapper"],
"homepage": "https://github.com/teqneers/PHP-Stream-Wrapper-for-Git",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Stefan Gehrig",
"email": "gehrig@teqneers.de",
"homepage": "http://opensource.teqneers.de/"
}
],
"require": {
"php": ">=5.3.0"
},
"autoload": {
"psr-0": {
"TQ\\Git": "src/TQ"
}
}
}

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="tests/bootstrap.php"
>
<php>
<const name="GIT_BINARY" value="/usr/bin/git"/>
<const name="TEST_REPO_PATH" value="false"/>
</php>
<testsuites>
<testsuite name="TEQneers Git Streamwrapper Test Suite">
<directory>./tests/TQ/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src/TQ/</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,203 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage Cli
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\Cli;
/**
* Encapsulates access to th Git command line binary
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage Cli
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class Binary
{
/**
* The file system path to the Git binary
*
* @var string
*/
protected $path;
/**
* Try to find the Git binary on the system
*
* @todo implement platform independant searching strategies
* @return string
*/
public static function locateBinary()
{
if (!self::isWindows()) {
$result = Call::create('which git')->execute();
return $result->getStdOut();
}
return '';
}
/**
* Checks if the current system is Windows
*
* @return boolean True if we're on a Windows machine
*/
protected static function isWindows()
{
return (strpos(PHP_OS, 'WIN') !== false);
}
/**
* Creates a Git binary interface
*
* If no path is given the class tries to find the correct
* binary {@see locateBinary()}
*
* @param string|null $path The path to the Git binary or NULL to auto-detect
*/
public function __construct($path = null)
{
if (!$path) {
$path = self::locateBinary();
}
if (!is_string($path) || empty($path)) {
throw new \InvalidArgumentException('No path to the Git binary found');
}
$this->path = $path;
}
/**
* Create a call to the Git binary for later execution
*
* @param string $path The full path to the Git repository
* @param string $command The Git command, e.g. show, commit or add
* @param array $arguments The command arguments
* @return Call
*/
public function createGitCall($path, $command, array $arguments)
{
$handleArg = function($key, $value) {
$key = ltrim($key, '-');
if (strlen($key) == 1) {
$arg = sprintf('-%s', escapeshellarg($key));
if ($value !== null) {
$arg .= ' '.escapeshellarg($value);
}
} else {
$arg = sprintf('--%s', escapeshellarg($key));
if ($value !== null) {
$arg .= '='.escapeshellarg($value);
}
}
return $arg;
};
if (!self::isWindows()) {
$binary = escapeshellcmd($this->path);
} else {
$binary = $this->path;
}
if (!empty($command)) {
$command = escapeshellarg($command);
}
$args = array();
$files = array();
$fileMode = false;
foreach ($arguments as $k => $v) {
if ($v === '--' || $k === '--') {
$fileMode = true;
continue;
}
if (is_int($k)) {
if (strpos($v, '-') === 0) {
$args[] = $handleArg($v, null);
} else if ($fileMode) {
$files[] = escapeshellarg($v);
} else {
$args[] = escapeshellarg($v);
}
} else {
if (strpos($k, '-') === 0) {
$args[] = $handleArg($k, $v);
}
}
}
$cmd = trim(sprintf('%s %s %s', $binary, $command, implode(' ', $args)));
if (count($files) > 0) {
$cmd .= ' -- '.implode(' ', $files);
}
$call = Call::create($cmd, $path);
return $call;
}
/**
* Method overloading - allows calling Git commands directly as class methods
*
* @param string $method The Git command, e.g. show, commit or add
* @param array $arguments The command arguments with the path to the Git
* repository beeing the first argument
* @return CallResult
*/
public function __call($method, array $arguments)
{
if (count($arguments) < 1) {
throw new \InvalidArgumentException(sprintf(
'"%s" must be called with at least one argument denoting the path', $method
));
}
$path = array_shift($arguments);
$args = array();
$stdIn = null;
if (count($arguments) > 0) {
$args = array_shift($arguments);
if (!is_array($args)) {
$args = array($args);
}
if (count($arguments) > 0) {
$stdIn = array_shift($arguments);
if (!is_string($stdIn)) {
$stdIn = null;
}
}
}
$call = $this->createGitCall($path, $method, $args);
return $call->execute($stdIn);
}
}

View File

@@ -0,0 +1,206 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage Cli
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\Cli;
/**
* Represents a single CLI call
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage Cli
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class Call
{
/**
* The CLI command to execute
*
* @var string
*/
protected $cmd;
/**
* The working directory in which the call will be executed
*
* @var string|null
*/
protected $cwd;
/**
* Environment variables - defaults to the current environment
*
* @var array|null
*/
protected $env;
/**
* Factory method to create a call
*
* @param string $cmd The CLI command to execute
* @param string|null $cwd The working directory in which the call will be executed
* @param array|null $env Environment variables - defaults to the current environment
* @return Call
*/
public static function create($cmd, $cwd = null, array $env = null) {
return new static($cmd, $cwd, $env);
}
/**
* Creates a new instance of a CLI call
*
* @param string $cmd The CLI command to execute
* @param string|null $cwd The working directory in which the call will be executed
* @param array|null $env Environment variables - defaults to the current environment
*/
public function __construct($cmd, $cwd = null, array $env = null)
{
$this->setCmd($cmd);
$this->setCwd($cwd);
$this->setEnv($env);
}
/**
* Returns the CLI command
*
* @return string
*/
public function getCmd()
{
return $this->cmd;
}
/**
* Sets the CLI command
*
* @param string $cmd The CLI command to execute
* @return Call
*/
public function setCmd($cmd)
{
$this->cmd = (string)$cmd;
return $this;
}
/**
* Returns the working directory for the call
*
* @return string|null
*/
public function getCwd()
{
return $this->cwd;
}
/**
* Sets the working directory for the call
*
* @param string|null $cwd The working directory in which the call will be executed
* @return Call
*/
public function setCwd($cwd)
{
if (empty($cwd)) {
$cwd = null;
} else {
$cwd = (string)$cwd;
}
$this->cwd = $cwd;
return $this;
}
/**
* Returns the environment variables for the call - if overridden
*
* @return array|null
*/
public function getEnv()
{
return $this->env;
}
/**
* Sets the environment variables for the call
*
* @param array|null $env Environment variables - defaults to the current environment
* @return Call
*/
public function setEnv(array $env = null)
{
$this->env = $env;
return $this;
}
/**
* Executes the call usign the preconfigured command
*
* @param string|null $stdIn Content that will be piped to the command
* @return CallResult
*/
public function execute($stdIn = null)
{
$stdOut = fopen('php://temp', 'r');
$stdErr = fopen('php://temp', 'r');
$descriptorSpec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => $stdOut, // stdout is a temp file that the child will write to
2 => $stdErr // stderr is a temp file that the child will write to
);
$pipes = array();
$process = proc_open(
$this->getCmd(),
$descriptorSpec,
$pipes,
$this->getCwd(),
$this->getEnv()
);
if (is_resource($process)) {
if ($stdIn !== null) {
fwrite($pipes[0], (string)$stdIn);
}
fclose($pipes[0]);
$returnCode = proc_close($process);
return new CallResult($this, $stdOut, $stdErr, $returnCode);
} else {
fclose($stdOut);
fclose($stdErr);
throw new \RuntimeException(sprintf('Cannot execute "%s"', $cmd));
}
}
}

View File

@@ -0,0 +1,72 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage Cli
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\Cli;
use TQ\Git\Exception;
/**
* Exception thrown when an error occured executing a CLI call
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage Cli
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class CallException extends \RuntimeException implements Exception
{
/**
* The call result that caused the exception
*
* @var CallResult
*/
protected $cliCallResult;
/**
* Creates a new exception
*
* @param string $message The exception message
* @param CallResult $cliCallResult The call result that caused the exception
*/
public function __construct($message, CallResult $cliCallResult)
{
$this->cliCallResult = $cliCallResult;
parent::__construct(
sprintf($message.' [%s]', $cliCallResult->getStdErr()),
$cliCallResult->getReturnCode()
);
}
}

View File

@@ -0,0 +1,226 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage Cli
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\Cli;
/**
* The result of a CLI call - provides access to stdout, stderror and the return code
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage Cli
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class CallResult
{
/**
* The stdout stream
*
* @var resource
*/
protected $stdOut;
/**
* True if there is a stdout
*
* @var boolean
*/
protected $hasStdOut;
/**
* The stderr stream
*
* @var resource
*/
protected $stdErr;
/**
* True if there is a stderr
*
* @var boolean
*/
protected $hasStdErr;
/**
* The return code
*
* @var integer
*/
protected $returnCode;
/**
* Reference to the call that resulted in this result
*
* @var Call
*/
protected $cliCall;
/**
* Creates a new result container for a CLI call
*
* @param Call $cliCall Reference to the call that resulted in this result
* @param resource $stdOut The stdout stream
* @param resource $stdErr The stderr stream
* @param integer $returnCode The return code
*/
public function __construct(Call $cliCall, $stdOut, $stdErr, $returnCode)
{
// @todo is there a better way to determine if a stream contains data?
fseek($stdOut, 0, SEEK_END);
$hasStdOut = (ftell($stdOut) > 0);
fseek($stdOut, 0, SEEK_SET);
// @todo is there a better way to determine if a stream contains data?
fseek($stdErr, 0, SEEK_END);
$hasStdErr = (ftell($stdErr) > 0);
fseek($stdErr, 0, SEEK_SET);
$this->cliCall = $cliCall;
$this->stdOut = $stdOut;
$this->hasStdOut = $hasStdOut;
$this->stdErr = $stdErr;
$this->hasStdErr = $hasStdErr;
$this->returnCode = (int)$returnCode;
}
/**
* Destructor closes the result and the internal stream resources
*/
public function __destruct()
{
$this->close();
}
/**
* Returns the reference to the call that resulted in this result
*
* @return Call
*/
public function getCliCall()
{
return $this->cliCall;
}
/**
* Returns the stdout stream
*
* @return resource
*/
public function getStdOutStream()
{
return $this->stdOut;
}
/**
* Returns the contents of stdout
*
* @return string
*/
public function getStdOut()
{
fseek($this->stdOut, 0, SEEK_SET);
return rtrim(stream_get_contents($this->stdOut));
}
/**
* Returns true if the call resulted in stdout to be populated
*
* @return boolean
*/
public function hasStdOut()
{
return $this->hasStdOut;
}
/**
* Returns the stderr stream
*
* @return resource
*/
public function getStdErrStream()
{
return $this->stdErr;
}
/**
* Returns the contents of stderr
*
* @return string
*/
public function getStdErr()
{
fseek($this->stdErr, 0, SEEK_SET);
return rtrim(stream_get_contents($this->stdErr));
}
/**
* Returns true if the call resulted in stderr to be populated
*
* @return boolean
*/
public function hasStdErr()
{
return $this->hasStdErr;
}
/**
* Returns the return code
*
* @return integer
*/
public function getReturnCode()
{
return $this->returnCode;
}
/**
* Closes the call result and the internal stream resources
*
* Prevents further usage
*/
public function close()
{
if ($this->stdOut !== null) {
fclose($this->stdOut);
$this->stdOut = null;
}
if ($this->stdErr !== null) {
fclose($this->stdErr);
$this->stdErr = null;
}
}
}

View File

@@ -0,0 +1,47 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git;
/**
* A marker interface to mark component exceptions
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
interface Exception {
}

View File

@@ -0,0 +1,894 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage Repository
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\Repository;
use TQ\Git\Cli\Binary;
use TQ\Git\Cli\CallResult;
use TQ\Git\Cli\CallException;
/**
* Provides access to a Git repository
*
* @uses TQ\Git\Cli\Binary
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage Repository
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class Repository
{
const RESET_STAGED = 1;
const RESET_WORKING = 2;
const RESET_ALL = 3;
const BRANCHES_LOCAL = 1;
const BRANCHES_REMOTE = 2;
const BRANCHES_ALL = 3;
/**
* The Git binary
*
* @var Binary
*/
protected $binary;
/**
* The repository path
*
* @var string
*/
protected $repositoryPath;
/**
* The mode used to create files when requested
*
* @var integer
*/
protected $fileCreationMode = 0644;
/**
* The mode used to create directories when requested
*
* @var integer
*/
protected $directoryCreationMode = 0755;
/**
* The author used when committing changes
*
* @var string
*/
protected $author;
/**
* Opens a Git repository on the file system, optionally creates and inits a new repository
*
* @param string $repositoryPath The full path to the repository
* @param Binary|null $binary The Git binary
* @param boolean|integer $createIfNotExists False to fail on non-existing repositories, directory
* creation mode, such as 0755 if the command
* should create the directory and init the repository instead
* @return Repository
*/
public static function open($repositoryPath, Binary $binary = null, $createIfNotExists = false)
{
if (!$binary) {
$binary = new Binary();
}
if (!is_string($repositoryPath)) {
throw new \InvalidArgumentException(sprintf(
'"%s" is not a valid path', $repositoryPath
));
}
$repositoryRoot = self::findRepositoryRoot($repositoryPath);
if ($repositoryRoot === null) {
if (!$createIfNotExists) {
throw new \InvalidArgumentException(sprintf(
'"%s" is not a valid path', $repositoryPath
));
} else {
if (!file_exists($repositoryPath) && !mkdir($repositoryPath, $createIfNotExists, true)) {
throw new \RuntimeException(sprintf(
'"%s" cannot be created', $repositoryPath
));
} else if (!is_dir($repositoryPath)) {
throw new \InvalidArgumentException(sprintf(
'"%s" is not a valid path', $repositoryPath
));
}
self::initRepository($binary, $repositoryPath);
$repositoryRoot = $repositoryPath;
}
}
if ($repositoryRoot === null) {
throw new \InvalidArgumentException(sprintf(
'"%s" is not a valid Git repository', $repositoryPath
));
}
return new static($repositoryRoot, $binary);
}
/**
* Inits a path to be used as a Git repository
*
* @param Binary $binary The Git binary
* @param string $path The repository path
*/
protected static function initRepository(Binary $binary, $path)
{
$result = $binary->init($path);
self::throwIfError($result, sprintf('Cannot initialize a Git repository in "%s"', $path));
}
/**
* Normalizes the directory separator to /
*
* @param string $path The path
* @return string The normalized path
*/
public static function normalizeDirectorySeparator($path)
{
return str_replace(DIRECTORY_SEPARATOR, '/', $path);
}
/**
* Tries to find the root directory for a given repository path
*
* @param string $path The file system path
* @return string|null NULL if the root cannot be found, the root path otherwise
*/
public static function findRepositoryRoot($path)
{
$found = null;
$path = self::normalizeDirectorySeparator($path);
$drive = null;
if (preg_match('~^(\w:)(.+)~', $path, $parts)) {
$drive = $parts[1];
$path = $parts[2];
}
$pathParts = explode('/', $path);
while (count($pathParts) > 0 && $found === null) {
$path = implode('/', $pathParts);
$gitDir = $path.'/'.'.git';
if (file_exists($gitDir) && is_dir($gitDir)) {
$found = $path;
}
array_pop($pathParts);
}
if ($drive && $found) {
$found = $drive.$found;
}
return $found;
}
/**
* Creates a new repository instance - use {@see open()} instead
*
* @param string $repositoryPath
* @param Binary $binary
*/
protected function __construct($repositoryPath, Binary $binary)
{
$this->binary = $binary;
$this->repositoryPath = rtrim($repositoryPath, '/');
}
/**
* Returns the Git binary
*
* @return Binary
*/
public function getBinary()
{
return $this->binary;
}
/**
* Returns the full file system path to the repository
*
* @return string
*/
public function getRepositoryPath()
{
return $this->repositoryPath;
}
/**
* Returns the mode used to create files when requested
*
* @return integer
*/
public function getFileCreationMode()
{
return $this->fileCreationMode;
}
/**
* Sets the mode used to create files when requested
*
* @param integer $fileCreationMode The mode, e.g. 644
* @return Repository
*/
public function setFileCreationMode($fileCreationMode)
{
$this->fileCreationMode = (int)$fileCreationMode;
return $this;
}
/**
* Returns the mode used to create directories when requested
*
* @return integer
*/
public function getDirectoryCreationMode()
{
return $this->directoryCreationMode;
}
/**
* Sets the mode used to create directories when requested
*
* @param integer $directoryCreationMode The mode, e.g. 755
* @return Repository
*/
public function setDirectoryCreationMode($directoryCreationMode)
{
$this->directoryCreationMode = (int)$directoryCreationMode;
return $this;
}
/**
* Returns the author used when committing changes
*
* @return string
*/
public function getAuthor()
{
return $this->author;
}
/**
* Sets the author used when committing changes
*
* @param string $author The author used when committing changes
* @return Repository
*/
public function setAuthor($author)
{
$this->author = (string)$author;
return $this;
}
/**
* Resolves an absolute path into a path relative to the repository path
*
* @param string|array $path A file system path (or an array of paths)
* @return string
*/
public function resolveLocalPath($path)
{
if (is_array($path)) {
$paths = array();
foreach ($path as $p) {
$paths[] = $this->resolveLocalPath($p);
}
return $paths;
} else {
$path = self::normalizeDirectorySeparator($path);
if (strpos($path, $this->getRepositoryPath()) === 0) {
$path = substr($path, strlen($this->getRepositoryPath()));
}
return ltrim($path, '/');
}
}
/**
* Resolves a path relative to the repository into an absolute path
*
* @param string|array $path A local path (or an array of paths)
* @return string
*/
public function resolveFullPath($path)
{
if (is_array($path)) {
$paths = array();
foreach ($path as $p) {
$paths[] = $this->resolveFullPath($p);
}
return $paths;
} else {
if (strpos($path, $this->getRepositoryPath()) === 0) {
return $path;
}
$path = self::normalizeDirectorySeparator($path);
$path = ltrim($path, '/');
return $this->getRepositoryPath().'/'.$path;
}
}
/**
* Returns the current commit hash
*
* @return string
*/
public function getCurrentCommit()
{
$result = $this->getBinary()->{'rev-parse'}($this->getRepositoryPath(), array(
'--verify',
'HEAD'
));
self::throwIfError($result, sprintf('Cannot rev-parse "%s"', $this->getRepositoryPath()));
return $result->getStdOut();
}
/**
* Commits the currently staged changes into the repository
*
* @param string $commitMsg The commit message
* @param array|null $file Restrict commit to the given files or NULL to commit all staged changes
* @param string|null $author The author
*/
public function commit($commitMsg, array $file = null, $author = null)
{
$author = $author ?: $this->getAuthor();
$args = array(
'--message' => $commitMsg
);
if ($author !== null) {
$args['--author'] = $author;
}
if ($file !== null) {
$args[] = '--';
$args = array_merge($args, $this->resolveLocalPath($file));
}
$result = $this->getBinary()->commit($this->getRepositoryPath(), $args);
self::throwIfError($result, sprintf('Cannot commit to "%s"', $this->getRepositoryPath()));
}
/**
* Resets the working directory and/or the staging area and discards all changes
*
* @param integer $what Bit mask to indicate which parts should be resetted
*/
public function reset($what = self::RESET_ALL)
{
$what = (int)$what;
if (($what & self::RESET_STAGED) == self::RESET_STAGED) {
$result = $this->getBinary()->reset($this->getRepositoryPath(), array('--hard'));
self::throwIfError($result, sprintf('Cannot reset "%s"', $this->getRepositoryPath()));
}
if (($what & self::RESET_WORKING) == self::RESET_WORKING) {
$result = $this->getBinary()->clean($this->getRepositoryPath(), array(
'--force',
'-x',
'-d'
));
self::throwIfError($result, sprintf('Cannot clean "%s"', $this->getRepositoryPath()));
}
}
/**
* Adds one or more files to the staging area
*
* @param array $file The file(s) to be added or NULL to add all new and/or changed files to the staging area
* @param boolean $force
*/
public function add(array $file = null, $force = false)
{
$args = array();
if ($force) {
$args[] = '--force';
}
if ($file !== null) {
$args[] = '--';
$args = array_merge($args, $this->resolveLocalPath($file));
} else {
$args[] = '--all';
}
$result = $this->getBinary()->add($this->getRepositoryPath(), $args);
self::throwIfError($result, sprintf('Cannot add "%s" to "%s"',
($file !== null) ? implode(', ', $file) : '*', $this->getRepositoryPath()
));
}
/**
* Removes one or more files from the repository but does not commit the changes
*
* @param array $file The file(s) to be removed
* @param boolean $recursive True to recursively remove subdirectories
* @param boolean $force True to continue even though Git reports a possible conflict
*/
public function remove(array $file, $recursive = false, $force = false)
{
$args = array();
if ($recursive) {
$args[] = '-r';
}
if ($force) {
$args[] = '--force';
}
$args[] = '--';
$args = array_merge($args, $this->resolveLocalPath($file));
$result = $this->getBinary()->rm($this->getRepositoryPath(), $args);
self::throwIfError($result, sprintf('Cannot remove "%s" from "%s"',
implode(', ', $file), $this->getRepositoryPath()
));
}
/**
* Renames a file but does not commit the changes
*
* @param string $fromPath The source path
* @param string $toPath The destination path
* @param boolean $force True to continue even though Git reports a possible conflict
*/
public function move($fromPath, $toPath, $force = false)
{
$args = array();
if ($force) {
$args[] = '--force';
}
$args[] = $this->resolveLocalPath($fromPath);
$args[] = $this->resolveLocalPath($toPath);
$result = $this->getBinary()->mv($this->getRepositoryPath(), $args);
self::throwIfError($result, sprintf('Cannot move "%s" to "%s" in "%s"',
$fromPath, $toPath, $this->getRepositoryPath()
));
}
/**
* Writes data to a file and commit the changes immediately
*
* @param string $path The file path
* @param scalar|array $data The data to write to the file
* @param string|null $commitMsg The commit message used when committing the changes
* @param integer|null $fileMode The mode for creating the file
* @param integer|null $dirMode The mode for creating the intermediate directories
* @param boolean $recursive Create intermediate directories recursively if required
* @param string|null $author The author
* @return string The current commit hash
*/
public function writeFile($path, $data, $commitMsg = null, $fileMode = null,
$dirMode = null, $recursive = true, $author = null
) {
$file = $this->resolveFullPath($path);
$fileMode = $fileMode ?: $this->getFileCreationMode();
$dirMode = $dirMode ?: $this->getDirectoryCreationMode();
$directory = dirname($file);
if (!file_exists($directory) && !mkdir($directory, (int)$dirMode, $recursive)) {
throw new \RuntimeException(sprintf('Cannot create "%s"', $directory));
} else if (!file_exists($file)) {
if (!touch($file)) {
throw new \RuntimeException(sprintf('Cannot create "%s"', $file));
}
if (!chmod($file, (int)$fileMode)) {
throw new \RuntimeException(sprintf('Cannot chmod "%s" to %d', $file, (int)$fileMode));
}
}
if (file_put_contents($file, $data) === false) {
throw new \RuntimeException(sprintf('Cannot write to "%s"', $file));
}
$this->add(array($file));
if ($commitMsg === null) {
$commitMsg = sprintf('%s created or changed file "%s"', __CLASS__, $path);
}
$this->commit($commitMsg, array($file), $author);
return $this->getCurrentCommit();
}
/**
* Removes a file and commit the changes immediately
*
* @param string $path The file path
* @param string|null $commitMsg The commit message used when committing the changes
* @param boolean $recursive True to recursively remove subdirectories
* @param boolean $force True to continue even though Git reports a possible conflict
* @param string|null $author The author
* @return string The current commit hash
*/
public function removeFile($path, $commitMsg = null, $recursive = false, $force = false, $author = null)
{
$this->remove(array($path), $recursive, $force);
if ($commitMsg === null) {
$commitMsg = sprintf('%s deleted file "%s"', __CLASS__, $path);
}
$this->commit($commitMsg, array($path), $author);
return $this->getCurrentCommit();
}
/**
* Renames a file and commit the changes immediately
*
* @param string $fromPath The source path
* @param string $toPath The destination path
* @param string|null $commitMsg The commit message used when committing the changes
* @param boolean $force True to continue even though Git reports a possible conflict
* @param string|null $author The author
* @return string The current commit hash
*/
public function renameFile($fromPath, $toPath, $commitMsg = null, $force = false, $author = null)
{
$this->move($fromPath, $toPath, $force);
if ($commitMsg === null) {
$commitMsg = sprintf('%s renamed/moved file "%s" to "%s"', __CLASS__, $fromPath, $toPath);
}
$this->commit($commitMsg, array($fromPath, $toPath), $author);
return $this->getCurrentCommit();
}
/**
* Returns the current repository log
*
* @param integer|null $limit The maximum number of log entries returned
* @param integer|null $skip Number of log entries that are skipped from the beginning
* @return string
*/
public function getLog($limit = null, $skip = null)
{
$arguments = array(
'--format' => 'fuller',
'--summary',
'-z'
);
if ($limit !== null) {
$arguments[] = sprintf('-%d', $limit);
}
if ($skip !== null) {
$arguments['--skip'] = (int)$skip;
}
$result = $this->getBinary()->log($this->getRepositoryPath(), $arguments);
self::throwIfError($result, sprintf('Cannot retrieve log from "%s"',
$this->getRepositoryPath()
));
$output = $result->getStdOut();
$log = array_map(function($f) {
return trim($f);
}, explode("\x0", $output));
return $log;
}
/**
* Returns a string containing information about the given commit
*
* @return string $hash The commit ref
* @return string
*/
public function showCommit($hash)
{
$result = $this->getBinary()->show($this->getRepositoryPath(), array(
'--format' => 'fuller',
$hash
));
self::throwIfError($result, sprintf('Cannot retrieve commit "%s" from "%s"',
$hash, $this->getRepositoryPath()
));
return $result->getStdOut();
}
/**
* Returns the content of a file at a given version
*
* @param string $file The path to the file
* @param string $ref The version ref
* @return string
*/
public function showFile($file, $ref = 'HEAD')
{
$result = $this->getBinary()->show($this->getRepositoryPath(), array(
sprintf('%s:%s', $ref, $file)
));
self::throwIfError($result, sprintf('Cannot show "%s" at "%s" from "%s"',
$file, $ref, $this->getRepositoryPath()
));
return $result->getStdOut();
}
/**
* Returns information about an object at a given version
*
* The information returned is an array with the following structure
* array(
* 'type' => blob|tree|commit,
* 'mode' => 0040000 for a tree, 0100000 for a blob, 0 otherwise,
* 'size' => the size
* )
*
* @param string $path The path to the object
* @param string $ref The version ref
* @return array The object info
*/
public function getObjectInfo($path, $ref = 'HEAD')
{
$info = array(
'type' => null,
'mode' => 0,
'size' => 0
);
$result = $this->getBinary()->{'cat-file'}($this->getRepositoryPath(), array(
'--batch-check'
), sprintf('%s:%s', $ref, $path));
self::throwIfError($result, sprintf('Cannot cat-file "%s" at "%s" from "%s"',
$path, $ref, $this->getRepositoryPath()
));
$output = trim($result->getStdOut());
$parts = array();
if (preg_match('/^(?<sha1>[0-9a-f]{40}) (?<type>\w+) (?<size>\d+)$/', $output, $parts)) {
$mode = 0;
switch ($parts['type']) {
case 'tree':
$mode |= 0040000;
break;
case 'blob':
$mode |= 0100000;
break;
}
$info['sha1'] = $parts['sha1'];
$info['type'] = $parts['type'];
$info['mode'] = (int)$mode;
$info['size'] = (int)$parts['size'];
}
return $info;
}
/**
* List the directory at a given version
*
* @param string $directory The path ot the directory
* @param string $ref The version ref
* @return array
*/
public function listDirectory($directory = '.', $ref = 'HEAD')
{
$directory = self::normalizeDirectorySeparator($directory);
$directory = rtrim($directory, '/').'/';
$path = $this->getRepositoryPath().'/'.$this->resolveLocalPath($directory);
$result = $this->getBinary()->{'ls-tree'}($path, array(
'--name-only',
'-z',
$ref
));
self::throwIfError($result, sprintf('Cannot list directory "%s" at "%s" from "%s"',
$directory, $ref, $this->getRepositoryPath()
));
$output = $result->getStdOut();
$listing = array_map(function($f) {
return trim($f);
}, explode("\x0", $output));
return $listing;
}
/**
* Returns the current status of the working directory and the stagin area
*
* The returned array structure is
* array(
* 'file' => '...',
* 'x' => '.',
* 'y' => '.',
* 'renamed' => null/'...'
* )
*
* @return array
*/
public function getStatus()
{
$result = $this->getBinary()->status($this->getRepositoryPath(), array(
'--short'
));
self::throwIfError($result,
sprintf('Cannot retrieve status from "%s"', $this->getRepositoryPath())
);
$output = rtrim($result->getStdOut());
if (empty($output)) {
return array();
}
$status = array_map(function($f) {
$line = rtrim($f);
$parts = array();
preg_match('/^(?<x>.)(?<y>.)\s(?<f>.+?)(?:\s->\s(?<f2>.+))?$/', $line, $parts);
$status = array(
'file' => $parts['f'],
'x' => trim($parts['x']),
'y' => trim($parts['y']),
'renamed' => (array_key_exists('f2', $parts)) ? $parts['f2'] : null
);
return $status;
}, explode("\n", $output));
return $status;
}
/**
* Returns true if there are uncommitted changes in the working directory and/or the staging area
*
* @return boolean
*/
public function isDirty()
{
$status = $this->getStatus();
return !empty($status);
}
/**
* Returns the name of the current branch
*
* @return string
*/
public function getCurrentBranch()
{
$result = $this->getBinary()->{'name-rev'}($this->getRepositoryPath(), array(
'--name-only',
'HEAD'
));
self::throwIfError($result,
sprintf('Cannot retrieve current branch from "%s"', $this->getRepositoryPath())
);
return $result->getStdOut();
}
/**
* Returns a list of the branches in the repository
*
* @param integer $which Which branches to retrieve (all, local or remote-tracking)
* @return array
*/
public function getBranches($which = self::BRANCHES_LOCAL)
{
$which = (int)$which;
$arguments = array(
'--no-color'
);
$local = (($which & self::BRANCHES_LOCAL) == self::BRANCHES_LOCAL);
$remote = (($which & self::BRANCHES_REMOTE) == self::BRANCHES_REMOTE);
if ($local && $remote) {
$arguments[] = '-a';
} else if ($remote) {
$arguments[] = '-r';
}
$result = $this->getBinary()->branch($this->getRepositoryPath(), $arguments);
self::throwIfError($result,
sprintf('Cannot retrieve branche from "%s"', $this->getRepositoryPath())
);
$output = rtrim($result->getStdOut());
if (empty($output)) {
return array();
}
$branches = array_map(function($b) {
$line = rtrim($b);
if (strpos($line, '* ') === 0) {
$line = substr($line, 2);
}
return $line;
}, explode("\n", $output));
return $branches;
}
/**
* Runs $function in a transactional scope committing all changes to the repository on success,
* but rolling back all changes in the event of an Exception beeing thrown in the closure
*
* The closure $function will be called with a {@see TQ\Git\Repository\Transaction} as its only argument
*
* @param \Closure $function
* @return Transaction
*/
public function transactional(\Closure $function)
{
try {
$transaction = new Transaction($this);
$result = $function($transaction);
$this->add(null);
if ($this->isDirty()) {
$commitMsg = $transaction->getCommitMsg();
if (empty($commitMsg)) {
$commitMsg = sprintf(
'%s did a transactional commit in "%s"',
__CLASS__,
$this->getRepositoryPath()
);
}
$this->commit($commitMsg, null, $transaction->getAuthor());
}
$commitHash = $this->getCurrentCommit();
$transaction->setCommitHash($commitHash);
$transaction->setResult($result);
return $transaction;
} catch (\Exception $e) {
$this->reset();
throw $e;
}
}
/**
* Internal method that checks if the CLI call has succeeded and throws an Excetion otherwise
*
* @param CallResult $result The CLI result
* @param string $message The exception message
*/
protected static function throwIfError(CallResult $result, $message)
{
if ($result->getReturnCode() > 0) {
throw new CallException($message, $result);
}
}
}

View File

@@ -0,0 +1,220 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage Repository
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\Repository;
/**
* Encapsulates arguments passed to and from a transactional piece of coce
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage Repository
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class Transaction
{
/**
* The Git repository
*
* @var Repository
*/
protected $repository;
/**
* The commit message
*
* @var string|null
*/
protected $commitMsg;
/**
* The author
*
* @var string|null
*/
protected $author;
/**
* The return value of the transactional callback
*
* @var mixed
*/
protected $result;
/**
* The commit hash
*
* @var string|null
*/
protected $commitHash;
/**
* Creates a new transactional parameter
*
* @param Repository $binary The Git repository
*/
public function __construct(Repository $repository)
{
$this->repository = $repository;
}
/**
* Returns the Git repository
*
* @return Repository
*/
public function getRepository()
{
return $this->repository;
}
/**
* Returns the full file system path to the Git repository
*
* @return string
*/
public function getRepositoryPath()
{
return $this->getRepository()->getRepositoryPath();
}
/**
* Resolves a path relative to the repository into an absolute path
*
* @param string $path The relative path to convert to an absolute path
* @return string
*/
public function resolvePath($path)
{
return $this->getRepository()->resolveFullPath($path);
}
/**
* Returns the commit message that will be used when comitting the transaction
*
* @return string|null
*/
public function getCommitMsg()
{
return $this->commitMsg;
}
/**
* Sets the commit message that will be used when comitting the transaction
*
* @param string|null $commitMsg The commit message
* @return Transaction
*/
public function setCommitMsg($commitMsg)
{
if ($commitMsg === null) {
$this->commitMsg = null;
} else {
$this->commitMsg = (string)$commitMsg;
}
return $this;
}
/**
* Returns the author that will be used when comitting the transaction
*
* @return string|null
*/
public function getAuthor()
{
return $this->author;
}
/**
* Sets the author that will be used when comitting the transaction
*
* @param string|null $author The author
* @return Transaction
*/
public function setAuthor($author)
{
if ($author === null) {
$this->author = null;
} else {
$this->author = (string)$author;
}
return $this;
}
/**
* Returns the return value of the closure executed in the transactional scope
*
* @return mixed
*/
public function getResult()
{
return $this->result;
}
/**
* Sets the return value of the closure executed in the transactional scope
*
* @param mixed $result The return value
* @return Transaction
*/
public function setResult($result)
{
$this->result = $result;
return $this;
}
/**
* Returns the hash identifiying the commit
*
* @return string|null
*/
public function getCommitHash()
{
return $this->commitHash;
}
/**
* Sets the hash identifiying the commit
*
* @param string $result
* @return Transaction
*/
public function setCommitHash($commitHash)
{
$this->commitHash = $commitHash;
return $this;
}
}

View File

@@ -0,0 +1,119 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\StreamWrapper;
/**
* Simple class to iterate over the results of a directory listing
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class DirectoryBuffer implements \Iterator
{
/**
* The directory listing
*
* @var array
*/
protected $listing;
/**
* Creates a directory buffer from an array
*
* @param array $listing The directory listing
*/
public function __construct(array $listing)
{
$this->listing = $listing;
reset($this->listing);
}
/**
* Implements Iterator
*
* @link http://php.net/manual/en/iterator.current.php
* @return string
*/
public function current()
{
return current($this->listing);
}
/**
* Implements Iterator
*
* @link http://php.net/manual/en/iterator.next.php
*/
public function next()
{
next($this->listing);
}
/**
* Implements Iterator
*
* @link http://php.net/manual/en/iterator.key.php
* @return integer|boolean False on failure
*/
public function key()
{
return key($this->listing);
}
/**
* Implements Iterator
*
* @link http://php.net/manual/en/iterator.valid.php
* @return boolean
*/
public function valid()
{
return (key($this->listing) !== null);
}
/**
* Implements Iterator
*
* @link http://php.net/manual/en/iterator.rewind.php
*/
public function rewind()
{
reset($this->listing);
}
}

View File

@@ -0,0 +1,77 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\StreamWrapper\FileBuffer\Factory;
use TQ\Git\StreamWrapper\PathInformation;
use TQ\Git\StreamWrapper\FileBuffer\StringBuffer;
/**
* Factory to create a commit buffer
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class CommitFactory implements Factory
{
/**
* Returns true if this factory can handle the requested path
*
* @param PathInformation $path The path information
* @param string $mode The mode used to open the file
* @return boolean True if this factory can handle the path
*/
public function canHandle(PathInformation $path, $mode)
{
return $path->hasArgument('commit');
}
/**
* Returns the file stream to handle the requested path
*
* @param PathInformation $path The path information
* @param string $mode The mode used to open the path
* @return FileBuffer The file buffer to handle the path
*/
public function createFileBuffer(PathInformation $path, $mode)
{
$repo = $path->getRepository();
$buffer = $repo->showCommit($path->getArgument('ref'));
return new StringBuffer($buffer, array(), 'r');
}
}

View File

@@ -0,0 +1,78 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\StreamWrapper\FileBuffer\Factory;
use TQ\Git\StreamWrapper\PathInformation;
use TQ\Git\StreamWrapper\FileBuffer\StringBuffer;
/**
* Factory to create a default buffer
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class DefaultFactory implements Factory
{
/**
* Returns true if this factory can handle the requested path
*
* @param PathInformation $path The path information
* @param string $mode The mode used to open the file
* @return boolean True if this factory can handle the path
*/
public function canHandle(PathInformation $path, $mode)
{
return true;
}
/**
* Returns the file stream to handle the requested path
*
* @param PathInformation $path The path information
* @param string $mode The mode used to open the path
* @return FileBuffer The file buffer to handle the path
*/
public function createFileBuffer(PathInformation $path, $mode)
{
$repo = $path->getRepository();
$buffer = $repo->showFile($path->getLocalPath(), $path->getRef());
$objectInfo = $repo->getObjectInfo($path->getLocalPath(), $path->getRef());
return new StringBuffer($buffer, $objectInfo, 'r');
}
}

View File

@@ -0,0 +1,68 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\StreamWrapper\FileBuffer\Factory;
use TQ\Git\StreamWrapper\PathInformation;
/**
* Interface to which file stream factories must adhere
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
interface Factory
{
/**
* Returns true if this factory can handle the requested path
*
* @param PathInformation $path The path information
* @param string $mode The mode used to open the file
* @return boolean True if this factory can handle the path
*/
function canHandle(PathInformation $path, $mode);
/**
* Returns the file stream to handle the requested path
*
* @param PathInformation $path The path information
* @param string $mode The mode used to open the path
* @return FileBuffer The file buffer to handle the path
*/
function createFileBuffer(PathInformation $path, $mode);
}

View File

@@ -0,0 +1,75 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\StreamWrapper\FileBuffer\Factory;
use TQ\Git\StreamWrapper\PathInformation;
use TQ\Git\StreamWrapper\FileBuffer\StreamBuffer;
/**
* Factory to create a HEAD file buffer
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class HeadFileFactory implements Factory
{
/**
* Returns true if this factory can handle the requested path
*
* @param PathInformation $path The path information
* @param string $mode The mode used to open the file
* @return boolean True if this factory can handle the path
*/
public function canHandle(PathInformation $path, $mode)
{
return $path->getRef() == 'HEAD' && !is_dir($path->getFullPath());
}
/**
* Returns the file stream to handle the requested path
*
* @param PathInformation $path The path information
* @param string $mode The mode used to open the path
* @return FileBuffer The file buffer to handle the path
*/
public function createFileBuffer(PathInformation $path, $mode)
{
return new StreamBuffer($path->getFullPath(), $mode);
}
}

View File

@@ -0,0 +1,80 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\StreamWrapper\FileBuffer\Factory;
use TQ\Git\StreamWrapper\PathInformation;
use TQ\Git\StreamWrapper\FileBuffer\StringBuffer;
/**
* Factory to create a log buffer
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class LogFactory implements Factory
{
/**
* Returns true if this factory can handle the requested path
*
* @param PathInformation $path The path information
* @param string $mode The mode used to open the file
* @return boolean True if this factory can handle the path
*/
public function canHandle(PathInformation $path, $mode)
{
return $path->hasArgument('log');
}
/**
* Returns the file stream to handle the requested path
*
* @param PathInformation $path The path information
* @param string $mode The mode used to open the path
* @return FileBuffer The file buffer to handle the path
*/
public function createFileBuffer(PathInformation $path, $mode)
{
$repo = $path->getRepository();
$buffer = implode(
str_repeat(PHP_EOL, 3),
$repo->getLog($path->getArgument('limit'), $path->getArgument('skip'))
);
return new StringBuffer($buffer, array(), 'r');
}
}

View File

@@ -0,0 +1,94 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\StreamWrapper\FileBuffer\Factory;
use TQ\Git\StreamWrapper\PathInformation;
/**
* Resolves the file stream factory to use on a stream_open call
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class Resolver
{
/**
* The list containing the possible factories
*
* @var \SplPriorityQueue
*/
protected $factoryList;
/**
* Creates a new factory resolver
*/
public function __construct()
{
$this->factoryList = new \SplPriorityQueue();
}
/**
* Adds a factory to the list of possible factories
*
* @param Factory $class The factory
* @param integer $priority The priority
* @return Resolver The resolver
*/
public function addFactory(Factory $factory, $priority = 10)
{
$this->factoryList->insert($factory, $priority);
return $this;
}
/**
* Returns the file stream factory to handle the requested path
*
* @param PathInformation $path The path information
* @param string $mode The mode used to open the path
* @return Factory The file buffer factory to handle the path
*/
public function findFactory(PathInformation $path, $mode)
{
foreach ($this->factoryList as $factory) {
if ($factory->canHandle($path, $mode)) {
return $factory;
}
}
throw new \RuntimeException('No factory found to handle the requested path');
}
}

View File

@@ -0,0 +1,113 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\StreamWrapper\FileBuffer;
/**
* Interface for file buffers used in the stream wrapper
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
interface FileBuffer
{
/**
* Returns the complete contents of the buffer
*
* @return string
*/
function getBuffer();
/**
* Returns true if the pointer is at the end of the buffer
*
* @return boolean
*/
function isEof();
/**
* Reads $count bytes from the buffer
*
* @param integer $count The number of bytes to read
* @return string|null
*/
function read($count);
/**
* Writes the given date into the buffer at the current pointer position
*
* @param string $data The data to write
* @return integer The number of bytes written
*/
function write($data);
/**
* Returns the current pointer position
*
* @return integer
*/
function getPosition();
/**
* Sets the pointer position
*
* @param integer $position The position in bytes
* @param integer $whence The reference from where to measure $position (SEEK_SET, SEEK_CUR or SEEK_END)
* @return boolean True if the position could be set
*/
function setPosition($position, $whence);
/**
* Returns the stat information for the buffer
*
* @return array
*/
function getStat();
/**
* Flushes the buffer to the storage media
*
* @return boolean
*/
function flush();
/**
* Closes the buffer
*/
function close();
}

View File

@@ -0,0 +1,176 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\StreamWrapper\FileBuffer;
/**
* Encapsulates a file stream buffer to be used in the streamwrapper
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class StreamBuffer implements FileBuffer
{
/**
* The file resource
*
* @var resource
*/
protected $stream;
/**
* Creates a neww file buffer with the given path
*
* @param string $path The path
* @param string $mode The file mode
*/
public function __construct($path, $mode = 'r+')
{
$this->stream = @fopen($path, $mode);
if ($this->stream === false) {
throw new StreamException(sprintf('Cannot access "%s" in mode "%s"', $path, $mode));
}
}
/**
* Destructor closes file stream handle
*/
public function __destruct()
{
$this->close();
}
/**
* Returns the complete contents of the buffer
*
* @return string
*/
public function getBuffer()
{
$currentPos = $this->getPosition();
$this->setPosition(0, SEEK_SET);
$buffer = stream_get_contents($this->stream);
$this->setPosition($currentPos, SEEK_SET);
return $buffer;
}
/**
* Returns true if the pointer is at the end of the buffer
*
* @return boolean
*/
public function isEof()
{
return feof($this->stream);
}
/**
* Reads $count bytes from the buffer
*
* @param integer $count The number of bytes to read
* @return string|null
*/
public function read($count)
{
return fread($this->stream, $count);
}
/**
* Writes the given date into the buffer at the current pointer position
*
* @param string $data The data to write
* @return integer The number of bytes written
*/
public function write($data)
{
return fwrite($this->stream, $data);
}
/**
* Returns the current pointer position
*
* @return integer
*/
public function getPosition()
{
return ftell($this->stream);
}
/**
* Sets the pointer position
*
* @param integer $position The position in bytes
* @param integer $whence The reference from where to measure $position (SEEK_SET, SEEK_CUR or SEEK_END)
* @return boolean True if the position could be set
*/
public function setPosition($position, $whence)
{
return (fseek($this->stream, $position, $whence) == 0);
}
/**
* Returns the stat information for the buffer
*
* @return array
*/
public function getStat()
{
return fstat($this->stream);
}
/**
* Flushes the buffer to the storage media
*
* @return boolean
*/
public function flush()
{
return fflush($this->stream);
}
/**
* Closes the buffer
*/
public function close()
{
if ($this->stream !== null) {
fclose($this->stream);
$this->stream = null;
}
}
}

View File

@@ -0,0 +1,50 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\StreamWrapper\FileBuffer;
use TQ\Git\Exception;
/**
* Exception thrown when an error occured executing a CLI call
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class StreamException extends \RuntimeException implements Exception
{
}

View File

@@ -0,0 +1,253 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\StreamWrapper\FileBuffer;
/**
* Encapsulates a file revision buffer to be used in the streamwrapper
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class StringBuffer implements FileBuffer
{
/**
* The buffer contents
*
* @var string
*/
protected $buffer;
/**
* The buffer length
*
* @var integer
*/
protected $length;
/**
* The current pointer position
*
* @var integer
*/
protected $position;
/**
* The object info if available
*
* @var array
*/
protected $objectInfo;
/**
* The read/write mode
*
* @var string
*/
protected $mode;
/**
* Creates a neww file buffer with the given contents
*
* @param string $content The contents
* @param array $objectInfo The object info
* @param string $mode The file mode
*/
public function __construct($buffer, array $objectInfo = array(), $mode = 'r+')
{
$this->buffer = $buffer;
$this->length = strlen($buffer);
$this->position = 0;
$this->objectInfo = $objectInfo;
$this->mode = $mode;
}
/**
* Returns the complete contents of the buffer
*
* @return string
*/
public function getBuffer()
{
return $this->buffer;
}
/**
* Returns true if the pointer is at the end of the buffer
*
* @return boolean
*/
public function isEof()
{
return ($this->position > $this->length);
}
/**
* Reads $count bytes from the buffer
*
* @param integer $count The number of bytes to read
* @return string|null
*/
public function read($count)
{
if ($this->isEof()) {
return null;
}
$buffer = substr($this->buffer, $this->position, $count);
$this->position += $count;
return $buffer;
}
/**
* Writes the given date into the buffer at the current pointer position
*
* @param string $data The data to write
* @return integer The number of bytes written
*/
public function write($data)
{
$dataLength = strlen($data);
$start = substr($this->buffer, 0, $this->position);
$end = substr($this->buffer, $this->position + $dataLength);
$this->buffer = $start.$data.$end;
$this->length = strlen($this->buffer);
$this->position += $dataLength;
return $dataLength;
}
/**
* Returns the current pointer position
*
* @return integer
*/
public function getPosition()
{
return $this->position;
}
/**
* Returns the current length
*
* @return integer
*/
public function getLength()
{
return $this->length;
}
/**
* Sets the pointer position
*
* @param integer $position The position in bytes
* @param integer $whence The reference from where to measure $position (SEEK_SET, SEEK_CUR or SEEK_END)
* @return boolean True if the position could be set
*/
public function setPosition($position, $whence)
{
switch ($whence) {
case SEEK_SET:
$this->position = $position;
break;
case SEEK_CUR:
$this->position += $position;
break;
case SEEK_END:
$this->position = $this->length + $position;
break;
default:
$this->position = 0;
return false;
}
if ($this->position < 0) {
$this->position = 0;
return false;
} else if ($this->position > $this->length) {
$this->position = $this->length;
return false;
} else {
return true;
}
}
/**
* Returns the stat information for the buffer
*
* @return array
*/
public function getStat()
{
$stat = array(
'ino' => 0,
'mode' => (array_key_exists('mode', $this->objectInfo)) ? $this->objectInfo['mode'] : 0,
'nlink' => 0,
'uid' => 0,
'gid' => 0,
'rdev' => 0,
'size' => (array_key_exists('size', $this->objectInfo)) ? $this->objectInfo['size'] : 0,
'atime' => 0,
'mtime' => 0,
'ctime' => 0,
'blksize' => -1,
'blocks' => -1,
);
return array_merge($stat, array_values($stat));
}
/**
* Flushes the buffer to the storage media
*
* @return boolean
*/
public function flush()
{
// no needed for the string buffer
return true;
}
/**
* Closes the buffer
*/
public function close()
{
$this->buffer = null;
$this->length = null;
$this->position = null;
$this->objectInfo = null;
}
}

View File

@@ -0,0 +1,235 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\StreamWrapper;
use TQ\Git\Repository\Repository;
use TQ\Git\Cli\Binary;
/**
* Handles decomposition of a given Git streamwrapper path
*
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class PathInformation
{
/**
* The Git repository
*
* @var Repository
*/
protected $repository;
/**
* The Git URL
*
* @var string
*/
protected $url;
/**
* The absolute path to the resource
*
* @var string
*/
protected $fullPath;
/**
* The relative path to the reosurce based on the repository path
*
* @var string
*/
protected $localPath;
/**
* The version ref
*
* @var string
*/
protected $ref;
/**
* Additional arguments
*
* @var array
*/
protected $arguments;
/**
* Returns path information for a given stream URL
*
* @param string $url The URL
* @param string $protocol The protocol registered
* @return array An array containing information about the path
*/
public static function parseUrl($url, $protocol)
{
// normalize directory separators
$path = str_replace(DIRECTORY_SEPARATOR, '/', $url);
$path = ltrim(substr($path, strlen($protocol) + 3), '/');
//fix path if fragment has been munged into the path (e.g. when using the RecursiveIterator)
$path = preg_replace('~^(.+?)(#[^/]+)(.*)$~', '$1$3$2', $path);
$url = parse_url($protocol.'://'.$path);
if (preg_match('~^\w:.+~', $path)) {
$url['path'] = $url['host'].':'.$url['path'];
} else {
$url['path'] = '/'.$url['host'].$url['path'];
}
unset($url['host']);
return $url;
}
/**
* Creates a new path information instance from a given URL
*
* @param string $url The URL
* @param string $protocol The protocol registered
* @param Binary $binary The Git binary
*/
public function __construct($url, $protocol, Binary $binary)
{
$url = self::parseUrl($url, $protocol);
$this->fullPath = $url['path'];
$this->repository = Repository::open($this->fullPath, $binary, false);
$this->localPath = $this->repository->resolveLocalPath($this->fullPath);
$this->ref = (array_key_exists('fragment', $url)) ? $url['fragment'] : 'HEAD';
$arguments = array();
if (array_key_exists('query', $url)) {
parse_str($url['query'], $arguments);
}
$this->arguments = $arguments;
$this->url = $protocol.'://'.$this->fullPath
.'#'.$this->ref
.'?'.http_build_query($this->arguments);
}
/**
* Returns the Git URL
*
* @return string
*/
public function getUrl()
{
return $this->url;
}
/**
* Returns the Git repository instance
*
* @return Repository
*/
public function getRepository()
{
return $this->repository;
}
/**
* Returns the absolute repository path
*
* @return string
*/
public function getRepositoryPath()
{
return $this->getRepository()->getRepositoryPath();
}
/**
* Returns the absolute path to the resource
*
* @return string
*/
public function getFullPath()
{
return $this->fullPath;
}
/**
* Returns the relative path to the resource based on the repository path
*
* @return string
*/
public function getLocalPath()
{
return $this->localPath;
}
/**
* Returns the version ref
*
* @return string
*/
public function getRef()
{
return $this->ref;
}
/**
* Returns the additional arguments given
*
* @return array
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Checks if the given argument exists
*
* @param string $argument The argument name
* @return boolean
*/
public function hasArgument($argument)
{
return array_key_exists($argument, $this->arguments);
}
/**
* Returns the given argument from the argument collection
*
* @param string $argument The argument name
* @return string|null The argument value or NULL if the argument does not exist
*/
public function getArgument($argument)
{
return ($this->hasArgument($argument)) ? $this->arguments[$argument] : null;
}
}

View File

@@ -0,0 +1,798 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Git Streamwrapper for PHP
*
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
/**
* @namespace
*/
namespace TQ\Git\StreamWrapper;
use TQ\Git\Cli\Binary;
use TQ\Git\Repository\Repository;
use TQ\Git\StreamWrapper\FileBuffer\FileBuffer;
use TQ\Git\StreamWrapper\FileBuffer\Factory\Resolver;
use TQ\Git\StreamWrapper\FileBuffer\Factory\CommitFactory;
use TQ\Git\StreamWrapper\FileBuffer\Factory\DefaultFactory;
use TQ\Git\StreamWrapper\FileBuffer\Factory\HeadFileFactory;
use TQ\Git\StreamWrapper\FileBuffer\Factory\LogFactory;
/**
* The streamwrapper that hooks into PHP's stream infrastructure
*
* @uses TQ\Git\Cli\Binary
* @uses TQ\Git\Repository\Repository;
* @uses TQ\Git\StreamWrapper\PathInformation
* @uses TQ\Git\StreamWrapper\DirectoryBuffer
* @uses TQ\Git\StreamWrapper\FileBuffer\Factory\Resolver
* @uses TQ\Git\StreamWrapper\FileBuffer\Factory\CommitFactory
* @uses TQ\Git\StreamWrapper\FileBuffer\Factory\DefaultFactory
* @uses TQ\Git\StreamWrapper\FileBuffer\Factory\HeadFileFactory
* @uses TQ\Git\StreamWrapper\FileBuffer\Factory\LogFactory
* @author Stefan Gehrig <gehrigteqneers.de>
* @category TQ
* @package TQ_Git
* @subpackage StreamWrapper
* @copyright Copyright (C) 2011 by TEQneers GmbH & Co. KG
*/
class StreamWrapper
{
/**
* The Git binary
*
* @var Binary
*/
protected static $binary;
/**
* The registered protocol
*
* @var string
*/
protected static $protocol;
/**
* The stream context if set
*
* @var resource
*/
public $context;
/**
* The parsed stream context options
*
* @var array
*/
protected $contextOptions;
/**
* The parsed stream context parameters
*
* @var array
*/
protected $contextParameters;
/**
* The directory buffer if used on a directory
*
* @var DirectoryBuffer
*/
protected $dirBuffer;
/**
* The file buffer if used on a file
*
* @var FileBuffer
*/
protected $fileBuffer;
/**
* The opened path
*
* @var PathInformation
*/
protected $path;
/**
* Registers the stream wrapper with the given protocol
*
* @param string $protocol The protocol (auch as "git")
* @param Binary|string $binary The Git binary
*/
public static function register($protocol, $binary = null)
{
if ($binary === null || is_string($binary)) {
$binary = new Binary($binary);
}
if (!($binary instanceof Binary)) {
throw new \InvalidArgumentException(sprintf('The $binary argument must either
be a TQ\Git\Binary instance or a path to the Git binary (%s given)',
(is_object($binary)) ? get_class($binary) : gettype($binary)
));
}
self::$binary = $binary;
if (!stream_wrapper_register($protocol, get_class(new static()))) {
throw new \RuntimeException(sprintf('The protocol "%s" is already registered with the
runtime or it cannot be registered', $protocol));
}
self::$protocol = $protocol;
}
/**
* Unregisters the stream wrapper
*/
public static function unregister()
{
if (!stream_wrapper_unregister(self::$protocol)) {
throw new \RuntimeException(sprintf('The protocol "%s" cannot be unregistered
from the runtime', self::$protocol));
}
}
/**
* Creates a new instance of the stream wrapper
*/
public function __construct()
{
}
/**
* Parses the passed stream context and returns the context options
* relevant for this stream wrapper
*
* @param boolean $all Return all options instead of just the relevant options
* @return array The context options
*/
protected function getContextOptions($all = false)
{
if ($this->contextOptions === null) {
$this->contextOptions = stream_context_get_options($this->context);
}
if (!$all && array_key_exists(self::$protocol, $this->contextOptions)) {
return $this->contextOptions[self::$protocol];
} else if ($all) {
return $this->contextOptions;
} else {
return array();
}
}
/**
* Returns a context option - $default if option is not found
*
* @param string $option The option to retrieve
* @param mixed $default The default value if $option is not found
*/
protected function getContextOption($option, $default = null)
{
$options = $this->getContextOptions();
if (array_key_exists($option, $options)) {
return $options[$option];
} else {
return $default;
}
}
/**
* Parses the passed stream context and returns the context parameters
*
* @return array The context parameters
*/
protected function getContextParameters()
{
if ($this->contextParameters === null) {
$this->contextParameters = stream_context_get_params($this->context);
}
return $this->contextParameters;
}
/**
* Returns a context parameter - $default if parameter is not found
*
* @param string $parameter The parameter to retrieve
* @param mixed $default The default value if $parameter is not found
*/
protected function getContextParameter($parameter, $default = null)
{
$parameters = $this->getContextParameters();
if (array_key_exists($parameter, $parameters)) {
return $parameters[$parameter];
} else {
return $default;
}
}
/**
* Returns the path information for a given stream URL
*
* @param string $streamUrl The URL given to the stream function
* @return PathInformation The path information representing the stream URL
*/
protected function getPath($streamUrl)
{
return new PathInformation($streamUrl, self::$protocol, self::$binary);
}
/**
* streamWrapper::dir_closedir — Close directory handle
*
* @return boolean Returns TRUE on success or FALSE on failure.
*/
public function dir_closedir()
{
$this->dirBuffer = null;
$this->path = null;
return true;
}
/**
* streamWrapper::dir_opendir — Open directory handle
*
* @param string $path Specifies the URL that was passed to {@see opendir()}.
* @param integer $options Whether or not to enforce safe_mode (0x04).
* @return boolean Returns TRUE on success or FALSE on failure.
*/
public function dir_opendir($path, $options)
{
try {
$path = $this->getPath($path);
$repo = $path->getRepository();
$listing = $repo->listDirectory($path->getLocalPath(), $path->getRef());
$this->dirBuffer = new DirectoryBuffer($listing);
$this->path = $path;
return true;
} catch (\Exception $e) {
trigger_error($e->getMessage(), E_USER_WARNING);
return false;
}
}
/**
* streamWrapper::dir_readdir — Read entry from directory handle
*
* @return string|false Should return string representing the next filename, or FALSE if there is no next file.
*/
public function dir_readdir()
{
$file = $this->dirBuffer->current();
$this->dirBuffer->next();
return $file;
}
/**
* streamWrapper::dir_rewinddir — Rewind directory handle
*
* @return boolean Returns TRUE on success or FALSE on failure.
*/
public function dir_rewinddir()
{
$this->dirBuffer->rewind();
return true;
}
/**
* streamWrapper::mkdir — Create a directory
*
* @param string $path Directory which should be created.
* @param integer $mode The value passed to {@see mkdir()}.
* @param integer $options A bitwise mask of values, such as STREAM_MKDIR_RECURSIVE.
* @return boolean Returns TRUE on success or FALSE on failure.
*/
public function mkdir($path, $mode, $options)
{
try {
$path = $this->getPath($path);
if ($path->getRef() != 'HEAD') {
throw new \Exception(sprintf(
'Cannot create a non-HEAD directory [%s#%s]', $path->getFullPath(), $path->getRef()
));
}
if (file_exists($path->getFullPath())) {
throw new \Exception(sprintf('Path %s already exists', $path->getFullPath()));
}
$recursive = self::maskHasFlag($options, STREAM_MKDIR_RECURSIVE);
$repo = $path->getRepository();
$commitMsg = $this->getContextOption('commitMsg', null);
$author = $this->getContextOption('author', null);
$repo->writeFile($path->getLocalPath().'/.gitkeep', '', $commitMsg, 0666, $mode, $recursive, $author);
return true;
} catch (\Exception $e) {
trigger_error($e->getMessage(), E_USER_WARNING);
return false;
}
}
/**
* streamWrapper::rename — Renames a file or directory
*
* @param string $path_from The URL to the current file.
* @param string $path_to The URL which the $path_from should be renamed to.
* @return boolean Returns TRUE on success or FALSE on failure.
*/
public function rename($path_from, $path_to)
{
try {
$pathFrom = $this->getPath($path_from);
if ($pathFrom->getRef() != 'HEAD') {
throw new \Exception(sprintf(
'Cannot rename a non-HEAD file [%s#%s]', $pathFrom->getFullPath(), $pathFrom->getRef()
));
}
if (!file_exists($pathFrom->getFullPath())) {
throw new \Exception(sprintf('Path %s not found', $pathFrom->getFullPath()));
}
if (!is_file($pathFrom->getFullPath())) {
throw new \Exception(sprintf('Path %s is not a file', $pathFrom->getFullPath()));
}
$pathTo = PathInformation::parseUrl($path_to, self::$protocol);
$pathTo = $pathTo['path'];
if (strpos($pathTo, $pathFrom->getRepositoryPath()) !== 0) {
throw new \Exception(sprintf('Cannot rename across repositories [%s -> %s]',
$pathFrom->getFullPath(), $pathTo));
}
$repo = $pathFrom->getRepository();
$commitMsg = $this->getContextOption('commitMsg', null);
$author = $this->getContextOption('author', null);
$repo->renameFile($pathFrom->getLocalPath(), $pathTo, $commitMsg, false, $author);
return true;
} catch (\Exception $e) {
trigger_error($e->getMessage(), E_USER_WARNING);
return false;
}
}
/**
* streamWrapper::rmdir — Removes a directory
*
* @param string $path The directory URL which should be removed.
* @param integer $options A bitwise mask of values, such as STREAM_MKDIR_RECURSIVE.
* @return boolean Returns TRUE on success or FALSE on failure.
*/
public function rmdir($path, $options)
{
try {
$path = $this->getPath($path);
if ($path->getRef() != 'HEAD') {
throw new \Exception(sprintf(
'Cannot remove a non-HEAD directory [%s#%s]', $path->getFullPath(), $path->getRef()
));
}
if (!file_exists($path->getFullPath())) {
throw new \Exception(sprintf('Path %s not found', $path->getFullPath()));
}
if (!is_dir($path->getFullPath())) {
throw new \Exception(sprintf('Path %s is not a directory', $path->getFullPath()));
}
$options |= STREAM_MKDIR_RECURSIVE;
$recursive = self::maskHasFlag($options, STREAM_MKDIR_RECURSIVE);
$repo = $path->getRepository();
$commitMsg = $this->getContextOption('commitMsg', null);
$author = $this->getContextOption('author', null);
$repo->removeFile($path->getLocalPath(), $commitMsg, $recursive, false, $author);
return true;
} catch (\Exception $e) {
trigger_error($e->getMessage(), E_USER_WARNING);
return false;
}
}
/**
* streamWrapper::stream_cast — Retrieve the underlaying resource
*
* @param integer $cast_as Can be STREAM_CAST_FOR_SELECT when stream_select() is calling stream_cast()
* or STREAM_CAST_AS_STREAM when stream_cast() is called for other uses.
* @return resource Should return the underlying stream resource used by the wrapper, or FALSE.
*/
/*
public function stream_cast($cast_as)
{
}
*/
/**
* streamWrapper::stream_close — Close an resource
*/
public function stream_close()
{
$this->fileBuffer->close();
$this->fileBuffer = null;
$repo = $this->path->getRepository();
$repo->add(array($this->path->getFullPath()));
if ($repo->isDirty()) {
$commitMsg = $this->getContextOption('commitMsg', null);
$author = $this->getContextOption('author', null);
$repo->commit($commitMsg, array($this->path->getFullPath()), $author);
}
$this->path = null;
}
/**
* streamWrapper::stream_eof — Tests for end-of-file on a file pointer
*
* @return boolean Should return TRUE if the read/write position is at the end of the stream
* and if no more data is available to be read, or FALSE otherwise.
*/
public function stream_eof()
{
return $this->fileBuffer->isEof();
}
/**
* streamWrapper::stream_flush — Flushes the output
*
* @return boolean Should return TRUE if the cached data was successfully stored
* (or if there was no data to store), or FALSE if the data could not be stored.
*/
public function stream_flush()
{
return $this->fileBuffer->flush();
}
/**
* streamWrapper::stream_lock — Advisory file locking
*
* @param integer $operation operation is one of the following:
* LOCK_SH to acquire a shared lock (reader).
* LOCK_EX to acquire an exclusive lock (writer).
* LOCK_UN to release a lock (shared or exclusive).
* LOCK_NB if you don't want flock() to block while locking. (not supported on Windows)
* @return boolean Returns TRUE on success or FALSE on failure.
*/
/*
public function stream_lock($operation)
{
}
*/
/**
* streamWrapper::stream_metadata — Change stream options
*
* @param string $path The file path or URL to set metadata. Note that in the case of a URL,
* it must be a :// delimited URL. Other URL forms are not supported.
* @param integer $option One of:
* PHP_STREAM_META_TOUCH (The method was called in response to touch())
* PHP_STREAM_META_OWNER_NAME (The method was called in response to chown() with string parameter)
* PHP_STREAM_META_OWNER (The method was called in response to chown())
* PHP_STREAM_META_GROUP_NAME (The method was called in response to chgrp())
* PHP_STREAM_META_GROUP (The method was called in response to chgrp())
* PHP_STREAM_META_ACCESS (The method was called in response to chmod())
* @param integer $var If option is
* PHP_STREAM_META_TOUCH: Array consisting of two arguments of the touch() function.
* PHP_STREAM_META_OWNER_NAME or PHP_STREAM_META_GROUP_NAME: The name of the owner
* user/group as string.
* PHP_STREAM_META_OWNER or PHP_STREAM_META_GROUP: The value owner user/group argument as integer.
* PHP_STREAM_META_ACCESS: The argument of the chmod() as integer.
* @return boolean Returns TRUE on success or FALSE on failure. If option is not implemented, FALSE should be returned.
*/
/*
public function stream_metadata($path, $option, $var)
{
}
*/
/**
* streamWrapper::stream_open — Opens file or URL
*
* @param string $path Specifies the URL that was passed to the original function.
* @param string $mode The mode used to open the file, as detailed for fopen().
* @param integer $options Holds additional flags set by the streams API. It can hold one or more of
* the following values OR'd together.
* STREAM_USE_PATH If path is relative, search for the resource using
* the include_path.
* STREAM_REPORT_ERRORS If this flag is set, you are responsible for raising
* errors using trigger_error() during opening of the
* stream. If this flag is not set, you should not raise
* any errors.
* @param string $opened_path If the path is opened successfully, and STREAM_USE_PATH is set in options, opened_path
* should be set to the full path of the file/resource that was actually opened.
* @return boolean Returns TRUE on success or FALSE on failure.
*/
public function stream_open($path, $mode, $options, &$opened_path)
{
try {
$path = $this->getPath($path);
$resolver = $this->createBufferFactoryResolver();
$factory = $resolver->findFactory($path, $mode);
$this->fileBuffer = $factory->createFileBuffer($path, $mode);
$this->path = $path;
if (self::maskHasFlag($options, STREAM_USE_PATH)) {
$opened_path = $this->path->getUrl();
}
return true;
} catch (\Exception $e) {
if (self::maskHasFlag($options, STREAM_REPORT_ERRORS)) {
trigger_error($e->getMessage(), E_USER_WARNING);
}
return false;
}
}
/**
* Creates the factory resolver
*
* @return Resolver
*/
protected function createBufferFactoryResolver()
{
$resolver = new Resolver();
$resolver->addFactory(new CommitFactory(), 100)
->addFactory(new LogFactory(), 90)
->addFactory(new HeadFileFactory(), 80)
->addFactory(new DefaultFactory(), -100);
return $resolver;
}
/**
* streamWrapper::stream_read — Read from stream
*
* @param integer $count How many bytes of data from the current position should be returned.
* @return string If there are less than count bytes available, return as many as are available.
* If no more data is available, return either FALSE or an empty string.
*/
public function stream_read($count)
{
$buffer = $this->fileBuffer->read($count);
if ($buffer === null) {
return false;
}
return $buffer;
}
/**
* streamWrapper::stream_seek — Seeks to specific location in a stream
*
* @param integer $offset The stream offset to seek to.
* @param integer $whence Possible values:
* SEEK_SET - Set position equal to offset bytes.
* SEEK_CUR - Set position to current location plus offset.
* SEEK_END - Set position to end-of-file plus offset.
* @return boolean Return TRUE if the position was updated, FALSE otherwise.
*/
public function stream_seek($offset, $whence = SEEK_SET)
{
return $this->fileBuffer->setPosition($offset, $whence);
}
/**
* streamWrapper::stream_set_option
*
* @param integer $option One of:
* STREAM_OPTION_BLOCKING (The method was called in response to stream_set_blocking())
* STREAM_OPTION_READ_TIMEOUT (The method was called in response to stream_set_timeout())
* STREAM_OPTION_WRITE_BUFFER (The method was called in response to stream_set_write_buffer())
* @param integer $arg1 If option is
* STREAM_OPTION_BLOCKING: requested blocking mode (1 meaning block 0 not blocking).
* STREAM_OPTION_READ_TIMEOUT: the timeout in seconds.
* STREAM_OPTION_WRITE_BUFFER: buffer mode (STREAM_BUFFER_NONE or STREAM_BUFFER_FULL).
* @param integer $arg2 If option is
* STREAM_OPTION_BLOCKING: This option is not set.
* STREAM_OPTION_READ_TIMEOUT: the timeout in microseconds.
* STREAM_OPTION_WRITE_BUFFER: the requested buffer size.
* @return boolean Returns TRUE on success or FALSE on failure. If option is not implemented,
* FALSE should be returned.
*/
/*
public function stream_set_option($option, $arg1, $arg2)
{
}
*/
/**
* streamWrapper::stream_stat — Retrieve information about a file resource
*
* @return array stat() and fstat() result format
* Numeric Associative (since PHP 4.0.6) Description
* 0 dev device number
* 1 ino inode number *
* 2 mode inode protection mode
* 3 nlink number of links
* 4 uid userid of owner *
* 5 gid groupid of owner *
* 6 rdev device type, if inode device
* 7 size size in bytes
* 8 atime time of last access (Unix timestamp)
* 9 mtime time of last modification (Unix timestamp)
* 10 ctime time of last inode change (Unix timestamp)
* 11 blksize blocksize of filesystem IO **
* 12 blocks number of 512-byte blocks allocated **
* * On Windows this will always be 0.
* ** Only valid on systems supporting the st_blksize type - other systems (e.g. Windows) return -1.
*/
public function stream_stat()
{
return $this->fileBuffer->getStat();
}
/**
* streamWrapper::stream_tell — Retrieve the current position of a stream
*
* @return integer Should return the current position of the stream.
*/
public function stream_tell()
{
return $this->fileBuffer->getPosition();
}
/**
* streamWrapper::stream_write — Write to stream
*
* @param string $data Should be stored into the underlying stream.
* @return integer Should return the number of bytes that were successfully stored, or 0 if none could be stored.
*/
public function stream_write($data)
{
return $this->fileBuffer->write($data);
}
/**
* streamWrapper::unlink — Delete a file
*
* @param string $path The file URL which should be deleted.
* @return boolean Returns TRUE on success or FALSE on failure.
*/
public function unlink($path)
{
try {
$path = $this->getPath($path);
if ($path->getRef() != 'HEAD') {
throw new \Exception(sprintf(
'Cannot unlink a non-HEAD file [%s#%s]', $path->getFullPath(), $path->getRef()
));
}
if (!file_exists($path->getFullPath())) {
throw new \Exception(sprintf('Path %s not found', $path->getFullPath()));
}
if (!is_file($path->getFullPath())) {
throw new \Exception(sprintf('Path %s is not a file', $path->getFullPath()));
}
$repo = $path->getRepository();
$commitMsg = $this->getContextOption('commitMsg', null);
$author = $this->getContextOption('author', null);
$repo->removeFile($path->getLocalPath(), $commitMsg, false, false, $author);
return true;
} catch (\Exception $e) {
trigger_error($e->getMessage(), E_USER_WARNING);
return false;
}
}
/**
* streamWrapper::url_stat — Retrieve information about a file
*
* mode bit mask:
* S_IFMT 0170000 bit mask for the file type bit fields
* S_IFSOCK 0140000 socket
* S_IFLNK 0120000 symbolic link
* S_IFREG 0100000 regular file
* S_IFBLK 0060000 block device
* S_IFDIR 0040000 directory
* S_IFCHR 0020000 character device
* S_IFIFO 0010000 FIFO
* S_ISUID 0004000 set UID bit
* S_ISGID 0002000 set-group-ID bit (see below)
* S_ISVTX 0001000 sticky bit (see below)
* S_IRWXU 00700 mask for file owner permissions
* S_IRUSR 00400 owner has read permission
* S_IWUSR 00200 owner has write permission
* S_IXUSR 00100 owner has execute permission
* S_IRWXG 00070 mask for group permissions
* S_IRGRP 00040 group has read permission
* S_IWGRP 00020 group has write permission
* S_IXGRP 00010 group has execute permission
* S_IRWXO 00007 mask for permissions for others (not in group)
* S_IROTH 00004 others have read permission
* S_IWOTH 00002 others have write permission
* S_IXOTH 00001 others have execute permission
*
* @param string $path The file path or URL to stat. Note that in the case of a URL, it must be a :// delimited URL.
* Other URL forms are not supported.
* @param integer $flags Holds additional flags set by the streams API. It can hold one or more of the following
* values OR'd together.
* STREAM_URL_STAT_LINK For resources with the ability to link to other resource (such
* as an HTTP Location: forward, or a filesystem symlink). This flag
* specified that only information about the link itself should be returned,
* not the resource pointed to by the link. This flag is set in response
* to calls to lstat(), is_link(), or filetype().
* STREAM_URL_STAT_QUIET If this flag is set, your wrapper should not raise any errors. If this
* flag is not set, you are responsible for reporting errors using the
* trigger_error() function during stating of the path.
* @return array Should return as many elements as stat() does. Unknown or unavailable values should be set to a
* rational value (usually 0).
*/
public function url_stat($path, $flags)
{
try {
$path = $this->getPath($path);
if ($path->getRef() == 'HEAD' && file_exists($path->getFullPath())) {
return stat($path->getFullPath());
} else {
$repo = $path->getRepository();
$info = $repo->getObjectInfo($path->getLocalPath(), $path->getRef());
$stat = array(
'ino' => 0,
'mode' => $info['mode'],
'nlink' => 0,
'uid' => 0,
'gid' => 0,
'rdev' => 0,
'size' => $info['size'],
'atime' => 0,
'mtime' => 0,
'ctime' => 0,
'blksize' => -1,
'blocks' => -1,
);
return array_merge($stat, array_values($stat));
}
} catch (\Exception $e) {
if (!self::maskHasFlag($flags, STREAM_URL_STAT_QUIET)) {
trigger_error($e->getMessage(), E_USER_WARNING);
}
return false;
}
}
/**
* Checks if a bitmask has a specific flag set
*
* @param integer $mask The bitmask
* @param integer $flag The flag to check
* @return boolean
*/
protected static function maskHasFlag($mask, $flag)
{
$flag = (int)$flag;
return ((int)$mask & $flag) === $flag;
}
}

View File

@@ -0,0 +1,125 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace TQ\Tests\Git\Cli;
use TQ\Git\Cli\Binary;
use TQ\Tests\Helper;
class CallCreationTest extends \PHPUnit_Framework_TestCase
{
protected function assertCliCommandEquals($expected, $actual)
{
if (strpos(PHP_OS, 'WIN') !== false) {
$expected = Helper::normalizeEscapeShellArg($expected);
}
$this->assertEquals($expected, $actual);
}
public function testHandleSingleDash()
{
$binary = new Binary('/usr/bin/git');
$call = $binary->createGitCall('/', 'command', array(
'-a'
));
$this->assertCliCommandEquals("/usr/bin/git 'command' -'a'", $call->getCmd());
}
public function testHandleDoubleDash()
{
$binary = new Binary('/usr/bin/git');
$call = $binary->createGitCall('/', 'command', array(
'--argument'
));
$this->assertCliCommandEquals("/usr/bin/git 'command' --'argument'", $call->getCmd());
}
public function testHandleSingleDashWithValue()
{
$binary = new Binary('/usr/bin/git');
$call = $binary->createGitCall('/', 'command', array(
'-a' => 'value'
));
$this->assertCliCommandEquals("/usr/bin/git 'command' -'a' 'value'", $call->getCmd());
}
public function testHandleDoubleDashWithValue()
{
$binary = new Binary('/usr/bin/git');
$call = $binary->createGitCall('/', 'command', array(
'--argument' => 'value'
));
$this->assertCliCommandEquals("/usr/bin/git 'command' --'argument'='value'", $call->getCmd());
}
public function testIgnoreLoneDoubleDash()
{
$binary = new Binary('/usr/bin/git');
$call = $binary->createGitCall('/', 'command', array(
'--'
));
$this->assertCliCommandEquals("/usr/bin/git 'command'", $call->getCmd());
}
public function testSimpleArgument()
{
$binary = new Binary('/usr/bin/git');
$call = $binary->createGitCall('/', 'command', array(
'option'
));
$this->assertCliCommandEquals("/usr/bin/git 'command' 'option'", $call->getCmd());
}
public function testFilePathAsArgument()
{
$binary = new Binary('/usr/bin/git');
$call = $binary->createGitCall('/', 'command', array(
'/path/to/file'
));
$this->assertCliCommandEquals("/usr/bin/git 'command' '/path/to/file'", $call->getCmd());
}
public function testFileModeSwitch()
{
$binary = new Binary('/usr/bin/git');
$call = $binary->createGitCall('/', 'command', array(
'option',
'--',
'path/to/file'
));
$this->assertCliCommandEquals("/usr/bin/git 'command' 'option' -- 'path/to/file'", $call->getCmd());
}
public function testFileModeSwitchWithFileArgument()
{
$binary = new Binary('/usr/bin/git');
$call = $binary->createGitCall('/', 'command', array(
'option',
'/path/to/file',
'--',
'path/to/file'
));
$this->assertCliCommandEquals("/usr/bin/git 'command' 'option' '/path/to/file' -- 'path/to/file'", $call->getCmd());
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace TQ\Tests\Git\Cli;
use TQ\Git\Cli\Binary;
use TQ\Git\Cli\CallResult;
class CallResultTest extends \PHPUnit_Framework_TestCase
{
public function testSuccessfulCall()
{
$binary = new Binary(GIT_BINARY);
$call = $binary->createGitCall('/', '', array(
'--version'
));
$result = $call->execute();
$this->assertTrue($result->hasStdOut());
$this->assertFalse($result->hasStdErr());
$this->assertEmpty($result->getStdErr());
$this->assertEquals(0, $result->getReturnCode());
$this->assertStringStartsWith('git version', $result->getStdOut());
$this->assertSame($call, $result->getCliCall());
}
public function testFailedCall()
{
$binary = new Binary(GIT_BINARY);
$call = $binary->createGitCall('/', 'unknowncommand', array());
$result = $call->execute();
$this->assertFalse($result->hasStdOut());
$this->assertTrue($result->hasStdErr());
$this->assertEmpty($result->getStdOut());
$this->assertNotEmpty($result->getStdErr());
$this->assertGreaterThan(0, $result->getReturnCode());
}
}

View File

@@ -0,0 +1,230 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace TQ\Tests\Git\Repository;
use TQ\Git\Cli\Binary;
use TQ\Git\Repository\Repository;
use TQ\Tests\Helper;
class InfoTest extends \PHPUnit_Framework_TestCase
{
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
Helper::removeDirectory(TESTS_TMP_PATH);
mkdir(TESTS_TMP_PATH, 0777, true);
mkdir(TESTS_REPO_PATH_1, 0777, true);
exec(sprintf('cd %s && %s init',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY
));
for ($i = 0; $i < 5; $i++) {
$file = sprintf('file_%d.txt', $i);
$path = TESTS_REPO_PATH_1.'/'.$file;
file_put_contents($path, sprintf('File %d', $i));
exec(sprintf('cd %s && %s add %s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg($file)
));
}
exec(sprintf('cd %s && %s commit --message=%s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg('Initial commit')
));
clearstatcache();
}
/**
* Tears down the fixture, for example, close a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
Helper::removeDirectory(TESTS_TMP_PATH);
}
/**
*
* @return Repository
*/
protected function getRepository()
{
return Repository::open(TESTS_REPO_PATH_1, new Binary(GIT_BINARY));
}
public function testGetCurrentBranch()
{
$c = $this->getRepository();
$this->assertEquals('master', $c->getCurrentBranch());
}
public function testGetBranches()
{
$c = $this->getRepository();
$this->assertEquals(array('master'), $c->getBranches());
}
public function testGetStatus()
{
$c = $this->getRepository();
$this->assertFalse($c->isDirty());
$file = TESTS_REPO_PATH_1.'/test.txt';
file_put_contents($file, 'Test');
$this->assertTrue($c->isDirty());
$status = $c->getStatus();
$this->assertEquals(array(
'file' => 'test.txt',
'x' => '?',
'y' => '?',
'renamed' => null
), $status[0]);
$c->add(array('test.txt'));
$this->assertTrue($c->isDirty());
$status = $c->getStatus();
$this->assertEquals(array(
'file' => 'test.txt',
'x' => 'A',
'y' => '',
'renamed' => null
), $status[0]);
$c->commit('Commt file', array('test.txt'));
$this->assertFalse($c->isDirty());
}
public function testGetLog()
{
$c = $this->getRepository();
$log = $c->getLog();
$this->assertEquals(1, count($log));
$this->assertContains('Initial commit', $log[0]);
$hash = $c->writeFile('/directory/test.txt', 'Test');
$log = $c->getLog();
$this->assertEquals(2, count($log));
$this->assertContains($hash, $log[0]);
$this->assertContains('Initial commit', $log[1]);
$log = $c->getLog(1);
$this->assertEquals(1, count($log));
$this->assertContains($hash, $log[0]);
$log = $c->getLog(1, 1);
$this->assertEquals(1, count($log));
$this->assertContains('Initial commit', $log[0]);
}
public function testShowCommit()
{
$c = $this->getRepository();
$hash = $c->writeFile('test.txt', 'Test');
$commit = $c->showCommit($hash);
$this->assertContains('test.txt', $commit);
$this->assertContains('Test', $commit);
}
public function testListDirectory()
{
$c = $this->getRepository();
$list = $c->listDirectory();
$this->assertContains('file_0.txt', $list);
$this->assertContains('file_1.txt', $list);
$this->assertContains('file_2.txt', $list);
$this->assertContains('file_3.txt', $list);
$this->assertContains('file_4.txt', $list);
$this->assertNotContains('test.txt', $list);
$c->writeFile('test.txt', 'Test');
$list = $c->listDirectory();
$this->assertContains('file_0.txt', $list);
$this->assertContains('file_1.txt', $list);
$this->assertContains('file_2.txt', $list);
$this->assertContains('file_3.txt', $list);
$this->assertContains('file_4.txt', $list);
$this->assertContains('test.txt', $list);
$c->removeFile('test.txt');
$list = $c->listDirectory();
$this->assertContains('file_0.txt', $list);
$this->assertContains('file_1.txt', $list);
$this->assertContains('file_2.txt', $list);
$this->assertContains('file_3.txt', $list);
$this->assertContains('file_4.txt', $list);
$this->assertNotContains('test.txt', $list);
$list = $c->listDirectory('.', 'HEAD^^');
$this->assertContains('file_0.txt', $list);
$this->assertContains('file_1.txt', $list);
$this->assertContains('file_2.txt', $list);
$this->assertContains('file_3.txt', $list);
$this->assertContains('file_4.txt', $list);
$this->assertNotContains('test.txt', $list);
$list = $c->listDirectory('.', 'HEAD^');
$this->assertContains('file_0.txt', $list);
$this->assertContains('file_1.txt', $list);
$this->assertContains('file_2.txt', $list);
$this->assertContains('file_3.txt', $list);
$this->assertContains('file_4.txt', $list);
$this->assertContains('test.txt', $list);
$c->writeFile('directory/test.txt', 'Test');
$list = $c->listDirectory('directory/', 'HEAD');
$this->assertContains('test.txt', $list);
$list = $c->listDirectory('directory', 'HEAD');
$this->assertContains('test.txt', $list);
}
public function testShowFile()
{
$c = $this->getRepository();
$this->assertEquals('File 0', $c->showFile('file_0.txt'));
$c->writeFile('test.txt', 'Test 1');
$this->assertEquals('Test 1', $c->showFile('test.txt'));
$c->writeFile('test.txt', 'Test 2');
$this->assertEquals('Test 2', $c->showFile('test.txt'));
$this->assertEquals('Test 1', $c->showFile('test.txt', 'HEAD^'));
$c->writeFile('test.txt', 'Test 3');
$this->assertEquals('Test 3', $c->showFile('test.txt'));
$this->assertEquals('Test 2', $c->showFile('test.txt', 'HEAD^'));
$this->assertEquals('Test 1', $c->showFile('test.txt', 'HEAD^^'));
}
}

View File

@@ -0,0 +1,338 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace TQ\Tests\Git\Repository;
use TQ\Git\Cli\Binary;
use TQ\Git\Repository\Repository;
use TQ\Git\Repository\Transaction;
use TQ\Tests\Helper;
class ModificationTest extends \PHPUnit_Framework_TestCase
{
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
Helper::removeDirectory(TESTS_TMP_PATH);
mkdir(TESTS_TMP_PATH, 0777, true);
mkdir(TESTS_REPO_PATH_1, 0777, true);
exec(sprintf('cd %s && %s init',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY
));
for ($i = 0; $i < 5; $i++) {
$file = sprintf('file_%d.txt', $i);
$path = TESTS_REPO_PATH_1.'/'.$file;
file_put_contents($path, sprintf('File %d', $i));
exec(sprintf('cd %s && %s add %s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg($file)
));
}
exec(sprintf('cd %s && %s commit --message=%s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg('Initial commit')
));
clearstatcache();
}
/**
* Tears down the fixture, for example, close a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
Helper::removeDirectory(TESTS_TMP_PATH);
}
/**
*
* @return Repository
*/
protected function getRepository()
{
return Repository::open(TESTS_REPO_PATH_1, new Binary(GIT_BINARY));
}
public function testAddFile()
{
$c = $this->getRepository();
$hash = $c->writeFile('test.txt', 'Test');
$this->assertEquals(40, strlen($hash));
$this->assertFileExists(TESTS_REPO_PATH_1.'/test.txt');
$this->assertEquals('Test', file_get_contents(TESTS_REPO_PATH_1.'/test.txt'));
$commit = $c->showCommit($hash);
$this->assertContains('+++ b/test.txt', $commit);
}
public function testAddFileInSubdirectory()
{
$c = $this->getRepository();
$hash = $c->writeFile('/directory/test.txt', 'Test');
$this->assertEquals(40, strlen($hash));
$this->assertFileExists(TESTS_REPO_PATH_1.'/directory/test.txt');
$this->assertEquals('Test', file_get_contents(TESTS_REPO_PATH_1.'/directory/test.txt'));
$commit = $c->showCommit($hash);
$this->assertContains('+++ b/directory/test.txt', $commit);
}
public function testAddMultipleFiles()
{
$c = $this->getRepository();
for ($i = 0; $i < 5; $i++) {
$hash = $c->writeFile(sprintf('test_%s.txt', $i), $i);
$this->assertEquals(40, strlen($hash));
$commit = $c->showCommit($hash);
$this->assertContains(sprintf('+++ b/test_%d.txt', $i), $commit);
}
for ($i = 0; $i < 5; $i++) {
$this->assertFileExists(TESTS_REPO_PATH_1.sprintf('/test_%s.txt', $i));
$this->assertEquals($i, file_get_contents(TESTS_REPO_PATH_1.sprintf('/test_%s.txt', $i)));
}
}
public function testRemoveFile()
{
$c = $this->getRepository();
$hash = $c->removeFile('file_0.txt');
$this->assertEquals(40, strlen($hash));
$this->assertFileNotExists(TESTS_REPO_PATH_1.'/file_0.txt');
$commit = $c->showCommit($hash);
$this->assertContains('--- a/file_0.txt', $commit);
}
public function testRemoveMultipleFiles()
{
$c = $this->getRepository();
for ($i = 0; $i < 5; $i++) {
$hash = $c->removeFile(sprintf('file_%s.txt', $i), $i);
$this->assertEquals(40, strlen($hash));
$commit = $c->showCommit($hash);
$this->assertContains(sprintf('--- a/file_%d.txt', $i), $commit);
}
for ($i = 0; $i < 5; $i++) {
$this->assertFileNotExists(TESTS_REPO_PATH_1.sprintf('/file_%d.txt', $i));
}
}
public function testRemoveWildcardFile()
{
$c = $this->getRepository();
$hash = $c->removeFile('file_*');
$this->assertEquals(40, strlen($hash));
for ($i = 0; $i < 5; $i++) {
$this->assertFileNotExists(TESTS_REPO_PATH_1.sprintf('/file_%d.txt', $i));
}
$commit = $c->showCommit($hash);
$this->assertContains('--- a/file_0.txt', $commit);
$this->assertContains('--- a/file_1.txt', $commit);
$this->assertContains('--- a/file_2.txt', $commit);
$this->assertContains('--- a/file_3.txt', $commit);
$this->assertContains('--- a/file_4.txt', $commit);
}
public function testRemoveSubdirectory()
{
$c = $this->getRepository();
$c->writeFile('subdirectory/.gitkeep', '');
$hash = $c->removeFile('subdirectory', null, true);
$this->assertEquals(40, strlen($hash));
$this->assertFileNotExists(TESTS_REPO_PATH_1.'/subdirectory');
$commit = $c->showCommit($hash);
$this->assertContains('deleted file', $commit);
}
public function testMoveFile()
{
$c = $this->getRepository();
$hash = $c->renameFile('file_0.txt', 'test.txt');
$this->assertEquals(40, strlen($hash));
$this->assertFileNotExists(TESTS_REPO_PATH_1.'/file_0.txt');
$this->assertFileExists(TESTS_REPO_PATH_1.'/test.txt');
$commit = $c->showCommit($hash);
$this->assertContains('--- a/file_0.txt', $commit);
$this->assertContains('+++ b/test.txt', $commit);
}
public function testReset()
{
$c = $this->getRepository();
$this->assertFalse($c->isDirty());
$file = TESTS_REPO_PATH_1.'/test.txt';
file_put_contents($file, 'Test');
$this->assertTrue($c->isDirty());
$c->reset();
$this->assertFalse($c->isDirty());
$this->assertFileNotExists($file);
file_put_contents($file, 'Test');
$c->add(array('test.txt'));
$c->reset();
$this->assertFalse($c->isDirty());
$this->assertFileNotExists($file);
file_put_contents($file, 'Test');
$this->assertTrue($c->isDirty());
$c->reset(Repository::RESET_WORKING);
$this->assertFalse($c->isDirty());
$this->assertFileNotExists($file);
file_put_contents($file, 'Test');
$c->add(array('test.txt'));
$this->assertTrue($c->isDirty());
$c->reset(Repository::RESET_WORKING);
$this->assertTrue($c->isDirty());
$this->assertFileExists($file);
$c->reset(Repository::RESET_STAGED);
$this->assertFalse($c->isDirty());
$this->assertFileNotExists($file);
}
public function testTransactionalChangesNoException()
{
$c = $this->getRepository();
$result = $c->transactional(function(Transaction $t) {
for ($i = 0; $i < 5; $i++) {
$file = $t->getRepositoryPath().'/'.sprintf('test_%s.txt', $i);
file_put_contents($file, 'Test');
}
$t->setCommitMsg('Hello World');
return 'This is the return value';
});
$this->assertEquals('This is the return value', $result->getResult());
$this->assertFalse($c->isDirty());
$list = $c->listDirectory();
$this->assertContains('file_0.txt', $list);
$this->assertContains('file_1.txt', $list);
$this->assertContains('file_2.txt', $list);
$this->assertContains('file_3.txt', $list);
$this->assertContains('file_4.txt', $list);
$this->assertContains('test_0.txt', $list);
$this->assertContains('test_1.txt', $list);
$this->assertContains('test_2.txt', $list);
$this->assertContains('test_3.txt', $list);
$this->assertContains('test_4.txt', $list);
$commit = $c->showCommit($result->getCommitHash());
$this->assertContains($result->getCommitMsg(), $commit);
$this->assertContains('+++ b/test_0.txt', $commit);
$this->assertContains('+++ b/test_1.txt', $commit);
$this->assertContains('+++ b/test_2.txt', $commit);
$this->assertContains('+++ b/test_3.txt', $commit);
$this->assertContains('+++ b/test_4.txt', $commit);
}
public function testTransactionalChangesException()
{
$c = $this->getRepository();
try {
$result = $c->transactional(function(Transaction $t) {
for ($i = 0; $i < 5; $i++) {
$file = $t->getRepositoryPath().'/'.sprintf('test_%s.txt', $i);
file_put_contents($file, 'Test');
}
throw new \Exception('Test');
});
$this->fail('Exception not thrown');
} catch (\Exception $e) {
$this->assertEquals('Test', $e->getMessage());
$this->assertFalse($c->isDirty());
}
}
public function testTransactionalChangesRenameAndDelete()
{
$c = $this->getRepository();
$result = $c->transactional(function(Transaction $t) {
unlink($t->resolvePath('file_0.txt'));
rename($t->resolvePath('file_1.txt'), $t->resolvePath('test.txt'));
$t->setCommitMsg('Hello World');
return 'This is the return value';
});
$this->assertEquals('This is the return value', $result->getResult());
$this->assertFalse($c->isDirty());
$list = $c->listDirectory();
$this->assertNotContains('file_0.txt', $list);
$this->assertNotContains('file_1.txt', $list);
$this->assertContains('test.txt', $list);
$this->assertContains('file_2.txt', $list);
$this->assertContains('file_3.txt', $list);
$this->assertContains('file_4.txt', $list);
$commit = $c->showCommit($result->getCommitHash());
$this->assertContains($result->getCommitMsg(), $commit);
$this->assertContains('--- a/file_0.txt', $commit);
$this->assertContains('--- a/file_1.txt', $commit);
$this->assertContains('+++ b/test.txt', $commit);
}
public function testTransactionalNoChanges()
{
$c = $this->getRepository();
$currentCommit = $c->getCurrentCommit();
$result = $c->transactional(function(Transaction $t) {
$t->setCommitMsg('Hello World');
return 'This is the return value';
});
$this->assertEquals('This is the return value', $result->getResult());
$this->assertEquals($currentCommit, $result->getCommitHash());
$this->assertEquals($currentCommit, $c->getCurrentCommit());
}
}

View File

@@ -0,0 +1,125 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace TQ\Tests\Git\Repository;
use TQ\Git\Cli\Binary;
use TQ\Git\Repository\Repository;
use TQ\Tests\Helper;
class SetupTest extends \PHPUnit_Framework_TestCase
{
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
Helper::removeDirectory(TESTS_TMP_PATH);
mkdir(TESTS_TMP_PATH, 0777, true);
mkdir(TESTS_REPO_PATH_1, 0777, true);
mkdir(TESTS_REPO_PATH_2, 0777, true);
exec(sprintf('cd %s && %s init',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY
));
clearstatcache();
}
/**
* Tears down the fixture, for example, close a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
Helper::removeDirectory(TESTS_TMP_PATH);
}
/**
*
* @param string $path
* @paran boolean|integer $create
* @return Repository
*/
protected function getRepository($path, $create = false)
{
return Repository::open($path, new Binary(GIT_BINARY), $create);
}
/**
* @expectedException InvalidArgumentException
*/
public function testRepositoryOpenOnNonExistantPath()
{
$c = $this->getRepository('/does/not/exist', false);
}
public function testRepositoryOpenOnFile()
{
$c = $this->getRepository(__FILE__, false);
$this->assertInstanceOf('TQ\Git\Repository\Repository', $c);
$this->assertEquals(PROJECT_PATH, $c->getRepositoryPath());
}
/**
* @expectedException InvalidArgumentException
*/
public function testRepositoryOpenOnNonRepositoryPath()
{
$c = $this->getRepository('/usr', false);
}
public function testRepositoryOpenOnRepositoryPath()
{
$c = $this->getRepository(TESTS_REPO_PATH_1, false);
$this->assertInstanceOf('TQ\Git\Repository\Repository', $c);
}
public function testRepositoryCreateOnExistingRepositoryPath()
{
$c = $this->getRepository(TESTS_REPO_PATH_1, 0755);
$this->assertInstanceOf('TQ\Git\Repository\Repository', $c);
}
public function testRepositoryCreateOnFile()
{
$c = $this->getRepository(__FILE__, 0755);
$this->assertInstanceOf('TQ\Git\Repository\Repository', $c);
$this->assertEquals(PROJECT_PATH, $c->getRepositoryPath());
}
public function testRepositoryCreateOnExistingPath()
{
$c = $this->getRepository(TESTS_REPO_PATH_2, 0755);
$this->assertInstanceOf('TQ\Git\Repository\Repository', $c);
}
public function testRepositoryCreateOnCreateablePath()
{
$c = $this->getRepository(TESTS_REPO_PATH_3, 0755);
$this->assertInstanceOf('TQ\Git\Repository\Repository', $c);
}
}

View File

@@ -0,0 +1,61 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace TQ\Tests\Git\StreamWrapper;
use TQ\Git\StreamWrapper\DirectoryBuffer;
class DirectoryBufferTest extends \PHPUnit_Framework_TestCase
{
public function testIteration()
{
$listing = array('a', 'b', 'c');
$iterator = new DirectoryBuffer($listing);
$i = 0;
while($iterator->valid()) {
$this->assertEquals($listing[$i], $iterator->current());
$this->assertEquals($i, $iterator->key());
$i++;
$iterator->next();
}
$this->assertEquals(count($listing), $i);
$iterator->rewind();
$this->assertEquals(reset($listing), $iterator->current());
$this->assertEquals(key($listing), $iterator->key());
}
public function testForeach()
{
$listing = array('a', 'b', 'c');
$iterator = new DirectoryBuffer($listing);
$i = 0;
foreach ($iterator as $k => $v) {
$this->assertEquals($listing[$i], $v);
$this->assertEquals($i, $k);
$i++;
}
$this->assertEquals(count($listing), $i);
}
}

View File

@@ -0,0 +1,471 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace TQ\Tests\Git\StreamWrapper;
use TQ\Git\Cli\Binary;
use TQ\Git\Repository\Repository;
use TQ\Git\StreamWrapper\StreamWrapper;
use TQ\Tests\Helper;
class DirectoryTest extends \PHPUnit_Framework_TestCase
{
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
Helper::removeDirectory(TESTS_TMP_PATH);
mkdir(TESTS_TMP_PATH, 0777, true);
mkdir(TESTS_REPO_PATH_1, 0777, true);
exec(sprintf('cd %s && %s init',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY
));
for ($i = 0; $i < 5; $i++) {
$file = sprintf('file_%d.txt', $i);
$path = TESTS_REPO_PATH_1.'/'.$file;
file_put_contents($path, sprintf('File %d', $i));
exec(sprintf('cd %s && %s add %s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg($file)
));
}
for ($i = 0; $i < 5; $i++) {
$dir = sprintf('dir_%d', $i);
$path = TESTS_REPO_PATH_1.'/'.$dir;
mkdir($path, 0777);
file_put_contents($path.'/file.txt', sprintf('Directory %d File', $i));
exec(sprintf('cd %s && %s add %s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg($path)
));
}
exec(sprintf('cd %s && %s commit --message=%s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg('Initial commit')
));
clearstatcache();
StreamWrapper::register('git', new Binary(GIT_BINARY));
}
/**
* Tears down the fixture, for example, close a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
Helper::removeDirectory(TESTS_TMP_PATH);
StreamWrapper::unregister();
}
/**
*
* @return Repository
*/
protected function getRepository()
{
return Repository::open(TESTS_REPO_PATH_1, new Binary(GIT_BINARY));
}
public function testListDirectory()
{
$dir = opendir('git://'.TESTS_REPO_PATH_1);
$i = 0;
while ($f = readdir($dir)) {
if ($i < 5) {
$this->assertEquals(sprintf('dir_%d', $i), $f);
} else {
$this->assertEquals(sprintf('file_%d.txt', $i % 5), $f);
}
$i++;
}
closedir($dir);
$this->assertEquals(10, $i);
}
public function testListSubDirectory()
{
$dir = opendir('git://'.TESTS_REPO_PATH_1.'/dir_0');
$i = 0;
while ($f = readdir($dir)) {
$this->assertEquals('file.txt', $f);
$i++;
}
closedir($dir);
$this->assertEquals(1, $i);
}
public function testListDirectoryWithRef()
{
$c = $this->getRepository();
$firstCommit = $c->writeFile('test_0.txt', 'Test 0');
$c->writeFile('test_1.txt', 'Test 1');
$dir = opendir('git://'.TESTS_REPO_PATH_1);
$i = 0;
while ($f = readdir($dir)) {
if ($i < 5) {
$this->assertEquals(sprintf('dir_%d', $i), $f);
} else if ($i < 10) {
$this->assertEquals(sprintf('file_%d.txt', $i % 5), $f);
} else {
$this->assertEquals(sprintf('test_%d.txt', $i % 10), $f);
}
$i++;
}
closedir($dir);
$this->assertEquals(12, $i);
$dir = opendir('git://'.TESTS_REPO_PATH_1.'#HEAD^');
$i = 0;
while ($f = readdir($dir)) {
if ($i < 5) {
$this->assertEquals(sprintf('dir_%d', $i), $f);
} else if ($i < 10) {
$this->assertEquals(sprintf('file_%d.txt', $i % 5), $f);
} else {
$this->assertEquals(sprintf('test_%d.txt', $i % 10), $f);
}
$i++;
}
closedir($dir);
$this->assertEquals(11, $i);
$dir = opendir('git://'.TESTS_REPO_PATH_1.'#HEAD^^');
$i = 0;
while ($f = readdir($dir)) {
if ($i < 5) {
$this->assertEquals(sprintf('dir_%d', $i), $f);
} else {
$this->assertEquals(sprintf('file_%d.txt', $i % 5), $f);
}
$i++;
}
closedir($dir);
$this->assertEquals(10, $i);
$dir = opendir('git://'.TESTS_REPO_PATH_1.'#'.$firstCommit);
$i = 0;
while ($f = readdir($dir)) {
if ($i < 5) {
$this->assertEquals(sprintf('dir_%d', $i), $f);
} else if ($i < 10) {
$this->assertEquals(sprintf('file_%d.txt', $i % 5), $f);
} else {
$this->assertEquals(sprintf('test_%d.txt', $i % 10), $f);
}
$i++;
}
closedir($dir);
$this->assertEquals(11, $i);
}
public function testListDirectoryWithIterator()
{
$dir = new \FilesystemIterator(
'git://'.TESTS_REPO_PATH_1,
\FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO
);
$i = 0;
foreach ($dir as $f => $fi) {
if ($i < 5) {
$this->assertEquals(sprintf('dir_%d', $i), $f);
} else {
$this->assertEquals(sprintf('file_%d.txt', $i % 5), $f);
}
$i++;
}
$this->assertEquals(10, $i);
}
public function testListDirectoryWithRecursiveIterator()
{
$dir = new \RecursiveDirectoryIterator(
'git://'.TESTS_REPO_PATH_1,
\FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO
);
$it = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::SELF_FIRST);
$i = 0;
foreach ($it as $f => $fi) {
if ($i < 10) {
if ($i % 2 === 0) {
$this->assertEquals(sprintf('dir_%d', $i / 2), $f);
} else {
$this->assertEquals('file.txt', $f);
}
} else {
$this->assertEquals(sprintf('file_%d.txt', $i % 5), $f);
}
$i++;
}
$this->assertEquals(15, $i);
}
public function testListDirectoryWithRefWithRecursiveIterator()
{
$c = $this->getRepository();
for ($i = 0; $i < 5; $i++) {
$dir = sprintf('dir_%d', $i);
$path = TESTS_REPO_PATH_1.'/'.$dir.'/test.txt';
$c->writeFile($path, 'Test');
}
$c->writeFile('test.txt', 'Test');
$dir = new \RecursiveDirectoryIterator(
'git://'.TESTS_REPO_PATH_1,
\FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO
);
$it = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::SELF_FIRST);
$i = 0;
$ex = array(
'dir_0',
'file.txt',
'test.txt',
'dir_1',
'file.txt',
'test.txt',
'dir_2',
'file.txt',
'test.txt',
'dir_3',
'file.txt',
'test.txt',
'dir_4',
'file.txt',
'test.txt',
'file_0.txt',
'file_1.txt',
'file_2.txt',
'file_3.txt',
'file_4.txt',
'test.txt'
);
foreach ($it as $f => $fi) {
$this->assertEquals($ex[$i], $f);
$i++;
}
$this->assertEquals(count($ex), $i);
$dir = new \RecursiveDirectoryIterator(
'git://'.TESTS_REPO_PATH_1.'#HEAD^',
\FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO
);
$it = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::SELF_FIRST);
$i = 0;
$ex = array(
'dir_0',
'file.txt',
'test.txt',
'dir_1',
'file.txt',
'test.txt',
'dir_2',
'file.txt',
'test.txt',
'dir_3',
'file.txt',
'test.txt',
'dir_4',
'file.txt',
'test.txt',
'file_0.txt',
'file_1.txt',
'file_2.txt',
'file_3.txt',
'file_4.txt',
);
foreach ($it as $f => $fi) {
$this->assertEquals($ex[$i], $f);
$i++;
}
$this->assertEquals(count($ex), $i);
$dir = new \RecursiveDirectoryIterator(
'git://'.TESTS_REPO_PATH_1.'#HEAD^^',
\FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO
);
$it = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::SELF_FIRST);
$i = 0;
$ex = array(
'dir_0',
'file.txt',
'test.txt',
'dir_1',
'file.txt',
'test.txt',
'dir_2',
'file.txt',
'test.txt',
'dir_3',
'file.txt',
'test.txt',
'dir_4',
'file.txt',
'file_0.txt',
'file_1.txt',
'file_2.txt',
'file_3.txt',
'file_4.txt'
);
foreach ($it as $f => $fi) {
$this->assertEquals($ex[$i], $f);
$i++;
}
$this->assertEquals(count($ex), $i);
$dir = new \RecursiveDirectoryIterator(
'git://'.TESTS_REPO_PATH_1.'#HEAD^^^',
\FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO
);
$it = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::SELF_FIRST);
$i = 0;
$ex = array(
'dir_0',
'file.txt',
'test.txt',
'dir_1',
'file.txt',
'test.txt',
'dir_2',
'file.txt',
'test.txt',
'dir_3',
'file.txt',
'dir_4',
'file.txt',
'file_0.txt',
'file_1.txt',
'file_2.txt',
'file_3.txt',
'file_4.txt'
);
foreach ($it as $f => $fi) {
$this->assertEquals($ex[$i], $f);
$i++;
}
$this->assertEquals(count($ex), $i);
$dir = new \RecursiveDirectoryIterator(
'git://'.TESTS_REPO_PATH_1.'#HEAD^^^^',
\FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO
);
$it = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::SELF_FIRST);
$i = 0;
$ex = array(
'dir_0',
'file.txt',
'test.txt',
'dir_1',
'file.txt',
'test.txt',
'dir_2',
'file.txt',
'dir_3',
'file.txt',
'dir_4',
'file.txt',
'file_0.txt',
'file_1.txt',
'file_2.txt',
'file_3.txt',
'file_4.txt'
);
foreach ($it as $f => $fi) {
$this->assertEquals($ex[$i], $f);
$i++;
}
$this->assertEquals(count($ex), $i);
$dir = new \RecursiveDirectoryIterator(
'git://'.TESTS_REPO_PATH_1.'#HEAD^^^^^',
\FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO
);
$it = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::SELF_FIRST);
$i = 0;
$ex = array(
'dir_0',
'file.txt',
'test.txt',
'dir_1',
'file.txt',
'dir_2',
'file.txt',
'dir_3',
'file.txt',
'dir_4',
'file.txt',
'file_0.txt',
'file_1.txt',
'file_2.txt',
'file_3.txt',
'file_4.txt'
);
foreach ($it as $f => $fi) {
$this->assertEquals($ex[$i], $f);
$i++;
}
$this->assertEquals(count($ex), $i);
$dir = new \RecursiveDirectoryIterator(
'git://'.TESTS_REPO_PATH_1.'#HEAD^^^^^^',
\FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO
);
$it = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::SELF_FIRST);
$i = 0;
$ex = array(
'dir_0',
'file.txt',
'dir_1',
'file.txt',
'dir_2',
'file.txt',
'dir_3',
'file.txt',
'dir_4',
'file.txt',
'file_0.txt',
'file_1.txt',
'file_2.txt',
'file_3.txt',
'file_4.txt'
);
foreach ($it as $f => $fi) {
$this->assertEquals($ex[$i], $f);
$i++;
}
$this->assertEquals(count($ex), $i);
}
}

View File

@@ -0,0 +1,123 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace TQ\Tests\Git\StreamWrapper\FileBuffer\Factory;
use TQ\Git\StreamWrapper\FileBuffer\Factory\Resolver;
use TQ\Git\StreamWrapper\FileBuffer\Factory\Factory;
use TQ\Git\StreamWrapper\PathInformation;
class ResolverTest extends \PHPUnit_Framework_TestCase
{
/**
* @return PathInformation
*/
protected function createPathMock()
{
return $this->getMock(
'TQ\Git\StreamWrapper\PathInformation',
array(),
array(),
'',
false
);
}
/**
* @return Factory
*/
protected function createFactoryMock()
{
return $this->getMock(
'TQ\Git\StreamWrapper\FileBuffer\Factory\Factory',
array('canHandle', 'createFileBuffer')
);
}
public function testReturnsFactoryWhichIsResponsible()
{
$resolver = new Resolver();
$factory1 = $this->createFactoryMock();
$factory1->expects($this->any())
->method('canHandle')
->will($this->returnValue(true));
$factory2 = $this->createFactoryMock();
$factory2->expects($this->any())
->method('canHandle')
->will($this->returnValue(false));
$resolver->addFactory($factory1, 10);
$resolver->addFactory($factory2, 30);
$path = $this->createPathMock();
$this->assertSame($factory1, $resolver->findFactory($path, 'r+'));
}
public function testReturnsFactoryWithHigherPriority()
{
$resolver = new Resolver();
$factory1 = $this->createFactoryMock();
$factory1->expects($this->any())
->method('canHandle')
->will($this->returnValue(true));
$factory2 = $this->createFactoryMock();
$factory2->expects($this->any())
->method('canHandle')
->will($this->returnValue(true));
$resolver->addFactory($factory1, 10);
$resolver->addFactory($factory2, 30);
$path = $this->createPathMock();
$this->assertSame($factory2, $resolver->findFactory($path, 'r+'));
}
/**
* @expectedException \RuntimeException
*/
public function testFailsWithoutAnyFactoryResponsible()
{
$resolver = new Resolver();
$factory1 = $this->createFactoryMock();
$factory1->expects($this->any())
->method('canHandle')
->will($this->returnValue(false));
$factory2 = $this->createFactoryMock();
$factory2->expects($this->any())
->method('canHandle')
->will($this->returnValue(false));
$resolver->addFactory($factory1, 10);
$resolver->addFactory($factory2, 30);
$path = $this->createPathMock();
$resolver->findFactory($path, 'r+');
}
}

View File

@@ -0,0 +1,158 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace TQ\Tests\Git\StreamWrapper\FileBuffer;
use TQ\Git\StreamWrapper\FileBuffer\StreamBuffer;
use TQ\Tests\Helper;
class StreamBufferTest extends \PHPUnit_Framework_TestCase
{
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
Helper::removeDirectory(TESTS_TMP_PATH);
mkdir(TESTS_TMP_PATH, 0777, true);
file_put_contents(TESTS_TMP_PATH.'/file_0.txt', 'File 0');
file_put_contents(TESTS_TMP_PATH.'/abc.txt', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
touch(TESTS_TMP_PATH.'/empty.txt');
clearstatcache();
}
/**
* Tears down the fixture, for example, close a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
Helper::removeDirectory(TESTS_TMP_PATH);
}
public function testReadByByte()
{
$expected = 'File 0';
$buffer = new StreamBuffer(TESTS_TMP_PATH.'/file_0.txt');
$expLength = strlen($expected);
for ($i = 0; $i < $expLength; $i++) {
$this->assertEquals($i, $buffer->getPosition());
$char = $buffer->read(1);
$this->assertEquals($expected[$i], $char);
$this->assertEquals($i + 1, $buffer->getPosition());
}
}
public function testSeek()
{
$expected = 'File 0';
$buffer = new StreamBuffer(TESTS_TMP_PATH.'/file_0.txt');
$buffer->setPosition(-1, SEEK_END);
$this->assertEquals('0', $buffer->read(1));
$this->assertEquals(6, $buffer->getPosition());
$this->assertFalse($buffer->isEof());
$this->assertEmpty($buffer->read(1));
$this->assertTrue($buffer->isEof());
$buffer->setPosition(0, SEEK_SET);
$this->assertEquals('F', $buffer->read(1));
$this->assertEquals(1, $buffer->getPosition());
$buffer->setPosition(3, SEEK_CUR);
$this->assertEquals(' ', $buffer->read(1));
$this->assertEquals(5, $buffer->getPosition());
$buffer->setPosition(-2, SEEK_CUR);
$this->assertEquals('e', $buffer->read(1));
$this->assertEquals(4, $buffer->getPosition());
}
public function testReadInReverse()
{
$buffer = new StreamBuffer(TESTS_TMP_PATH.'/file_0.txt');
$expected = '0 eliF';
$actual = '';
$buffer->setPosition(-1, SEEK_END);
while (($pos = $buffer->getPosition()) > 0) {
$actual .= $buffer->read(1);
$buffer->setPosition(-2, SEEK_CUR);
}
$actual .= $buffer->read(1);
$this->assertEquals($expected, $actual);
}
public function testWriteInMiddle()
{
$buffer = new StreamBuffer(TESTS_TMP_PATH.'/abc.txt');
$expected = 'ABC1234567890NOPQRSTUVWXYZ';
$buffer->setPosition(3, SEEK_SET);
$buffer->write('1234567890');
$actual = $buffer->getBuffer();
$this->assertEquals($expected, $actual);
}
public function testWriteAtStart()
{
$buffer = new StreamBuffer(TESTS_TMP_PATH.'/abc.txt');
$expected = '1234567890KLMNOPQRSTUVWXYZ';
$buffer->setPosition(0, SEEK_SET);
$buffer->write('1234567890');
$actual = $buffer->getBuffer();
$this->assertEquals($expected, $actual);
}
public function testWriteAtEnd()
{
$buffer = new StreamBuffer(TESTS_TMP_PATH.'/abc.txt');
$expected = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
$buffer->setPosition(0, SEEK_END);
$buffer->write('1234567890');
$actual = $buffer->getBuffer();
$this->assertEquals($expected, $actual);
}
public function testWriteOverlappingEnd()
{
$buffer = new StreamBuffer(TESTS_TMP_PATH.'/abc.txt');
$expected = 'ABCDEFGHIJKLMNOPQRSTUVW1234567890';
$buffer->setPosition(-3, SEEK_END);
$buffer->write('1234567890');
$actual = $buffer->getBuffer();
$this->assertEquals($expected, $actual);
}
public function testWriteInEmptyBuffer()
{
$buffer = new StreamBuffer(TESTS_TMP_PATH.'/empty.txt');
$expected = '1234567890';
$buffer->write('1234567890');
$actual = $buffer->getBuffer();
$this->assertEquals($expected, $actual);
}
}

View File

@@ -0,0 +1,173 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace TQ\Tests\Git\StreamWrapper\FileBuffer;
use TQ\Git\StreamWrapper\FileBuffer\StringBuffer;
class StringBufferTest extends \PHPUnit_Framework_TestCase
{
public function testReadByByte()
{
$expected = 'File 0';
$buffer = new StringBuffer($expected);
$expLength = strlen($expected);
for ($i = 0; $i < $expLength; $i++) {
$this->assertEquals($i, $buffer->getPosition());
$char = $buffer->read(1);
$this->assertEquals($expected[$i], $char);
$this->assertEquals($i + 1, $buffer->getPosition());
}
}
public function testSeek()
{
$expected = 'File 0';
$buffer = new StringBuffer($expected);
$buffer->setPosition(-1, SEEK_END);
$this->assertEquals('0', $buffer->read(1));
$this->assertEquals(6, $buffer->getPosition());
$this->assertFalse($buffer->isEof());
$this->assertEmpty($buffer->read(1));
$this->assertTrue($buffer->isEof());
$buffer->setPosition(0, SEEK_SET);
$this->assertEquals('F', $buffer->read(1));
$this->assertEquals(1, $buffer->getPosition());
$buffer->setPosition(3, SEEK_CUR);
$this->assertEquals(' ', $buffer->read(1));
$this->assertEquals(5, $buffer->getPosition());
$buffer->setPosition(-2, SEEK_CUR);
$this->assertEquals('e', $buffer->read(1));
$this->assertEquals(4, $buffer->getPosition());
}
public function testReadInReverse()
{
$buffer = new StringBuffer('File 0');
$expected = '0 eliF';
$actual = '';
$buffer->setPosition(-1, SEEK_END);
while (($pos = $buffer->getPosition()) > 0) {
$actual .= $buffer->read(1);
$buffer->setPosition(-2, SEEK_CUR);
}
$actual .= $buffer->read(1);
$this->assertEquals($expected, $actual);
}
public function testWriteInMiddle()
{
$buffer = new StringBuffer('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
$expected = 'ABC1234567890NOPQRSTUVWXYZ';
$buffer->setPosition(3, SEEK_SET);
$buffer->write('1234567890');
$actual = $buffer->getBuffer();
$this->assertEquals($expected, $actual);
}
public function testWriteAtStart()
{
$buffer = new StringBuffer('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
$expected = '1234567890KLMNOPQRSTUVWXYZ';
$buffer->setPosition(0, SEEK_SET);
$buffer->write('1234567890');
$actual = $buffer->getBuffer();
$this->assertEquals($expected, $actual);
}
public function testWriteAtEnd()
{
$buffer = new StringBuffer('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
$expected = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
$buffer->setPosition(0, SEEK_END);
$buffer->write('1234567890');
$actual = $buffer->getBuffer();
$this->assertEquals($expected, $actual);
}
public function testWriteOverlappingEnd()
{
$buffer = new StringBuffer('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
$expected = 'ABCDEFGHIJKLMNOPQRSTUVW1234567890';
$buffer->setPosition(-3, SEEK_END);
$buffer->write('1234567890');
$actual = $buffer->getBuffer();
$this->assertEquals($expected, $actual);
}
public function testWriteInEmptyBuffer()
{
$buffer = new StringBuffer('');
$expected = '1234567890';
$buffer->write('1234567890');
$actual = $buffer->getBuffer();
$this->assertEquals($expected, $actual);
}
public function testSeekBeyondLimits()
{
$buffer = new StringBuffer('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
$this->assertFalse($buffer->setPosition(-5, SEEK_SET));
$this->assertEquals(0, $buffer->getPosition());
$this->assertFalse($buffer->setPosition(4711, SEEK_SET));
$this->assertEquals(26, $buffer->getPosition());
}
public function testSeekWithIllegalWhence()
{
$buffer = new StringBuffer('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
$this->assertFalse($buffer->setPosition(5, 4711));
$this->assertEquals(0, $buffer->getPosition());
}
public function testLengthAndPositionUpdatedWithWrite()
{
$buffer = new StringBuffer('');
$this->assertEquals(0, $buffer->getPosition());
$this->assertEquals(0, $buffer->getLength());
$buffer->write('1');
$this->assertEquals(1, $buffer->getPosition());
$this->assertEquals(1, $buffer->getLength());
$buffer->write('23');
$this->assertEquals(3, $buffer->getPosition());
$this->assertEquals(3, $buffer->getLength());
$buffer->setPosition(-2, SEEK_CUR);
$this->assertEquals(1, $buffer->getPosition());
$buffer->write('4');
$this->assertEquals(2, $buffer->getPosition());
$this->assertEquals(3, $buffer->getLength());
$this->assertEquals('143', $buffer->getBuffer());
}
}

View File

@@ -0,0 +1,359 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace TQ\Tests\Git\StreamWrapper;
use TQ\Git\Cli\Binary;
use TQ\Git\Repository\Repository;
use TQ\Git\StreamWrapper\StreamWrapper;
use TQ\Tests\Helper;
class FileOperationTest extends \PHPUnit_Framework_TestCase
{
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
Helper::removeDirectory(TESTS_TMP_PATH);
mkdir(TESTS_TMP_PATH, 0777, true);
mkdir(TESTS_REPO_PATH_1, 0777, true);
exec(sprintf('cd %s && %s init',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY
));
for ($i = 0; $i < 5; $i++) {
$file = sprintf('file_%d.txt', $i);
$path = TESTS_REPO_PATH_1.'/'.$file;
file_put_contents($path, sprintf('File %d', $i));
exec(sprintf('cd %s && %s add %s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg($file)
));
}
exec(sprintf('cd %s && %s commit --message=%s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg('Initial commit')
));
clearstatcache();
StreamWrapper::register('git', new Binary(GIT_BINARY));
}
/**
* Tears down the fixture, for example, close a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
Helper::removeDirectory(TESTS_TMP_PATH);
StreamWrapper::unregister();
}
/**
*
* @return Repository
*/
protected function getRepository()
{
return Repository::open(TESTS_REPO_PATH_1, new Binary(GIT_BINARY));
}
public function testUnlinkFile()
{
$path = sprintf('git://%s/file_0.txt', TESTS_REPO_PATH_1);
unlink($path);
$this->assertFileNotExists(TESTS_REPO_PATH_1.'/file_0.txt');
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertContains('--- a/file_0.txt', $commit);
}
/**
* @expectedException PHPUnit_Framework_Error_Warning
*/
public function testUnlinkFileNonHead()
{
$path = sprintf('git://%s/file_0.txt#HEAD^', TESTS_REPO_PATH_1);
unlink($path);
}
/**
* @expectedException PHPUnit_Framework_Error_Warning
*/
public function testUnlinkNonExistantFile()
{
$path = sprintf('git://%s/file_does_not_exist.txt', TESTS_REPO_PATH_1);
unlink($path);
}
/**
* @expectedException PHPUnit_Framework_Error_Warning
*/
public function testUnlinkNonFile()
{
$c = $this->getRepository();
$c->writeFile('directory/test.txt', 'Test');
$path = sprintf('git://%s/directory', TESTS_REPO_PATH_1);
unlink($path);
}
public function testUnlinkFileWithContext()
{
$path = sprintf('git://%s/file_0.txt', TESTS_REPO_PATH_1);
$cntxt = stream_context_create(array(
'git' => array(
'commitMsg' => 'Hello World',
'author' => 'Luke Skywalker <skywalker@deathstar.com>'
)
));
unlink($path, $cntxt);
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertContains('Hello World', $commit);
$this->assertContains('Luke Skywalker <skywalker@deathstar.com>', $commit);
}
public function testRenameFile()
{
$pathFrom = sprintf('git://%s/file_0.txt', TESTS_REPO_PATH_1);
$pathTo = sprintf('git://%s/test.txt', TESTS_REPO_PATH_1);
rename($pathFrom, $pathTo);
$this->assertFileNotExists(TESTS_REPO_PATH_1.'/file_0.txt');
$this->assertFileExists(TESTS_REPO_PATH_1.'/test.txt');
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertContains('--- a/file_0.txt', $commit);
$this->assertContains('+++ b/test.txt', $commit);
}
/**
* @expectedException PHPUnit_Framework_Error_Warning
*/
public function testRenameFileNonHead()
{
$pathFrom = sprintf('git://%s/file_0.txt#HEAD^', TESTS_REPO_PATH_1);
$pathTo = sprintf('git://%s/test.txt', TESTS_REPO_PATH_1);
rename($pathFrom, $pathTo);
}
/**
* @expectedException PHPUnit_Framework_Error_Warning
*/
public function testRenameNonExistantFile()
{
$pathFrom = sprintf('git://%s/file_does_not_exist.txt', TESTS_REPO_PATH_1);
$pathTo = sprintf('git://%s/test.txt', TESTS_REPO_PATH_1);
rename($pathFrom, $pathTo);
}
/**
* @expectedException PHPUnit_Framework_Error_Warning
*/
public function testRenameNonFile()
{
$c = $this->getRepository();
$c->writeFile('directory/test.txt', 'Test');
$pathFrom = sprintf('git://%s/directory', TESTS_REPO_PATH_1);
$pathTo = sprintf('git://%s/test.txt', TESTS_REPO_PATH_1);
rename($pathFrom, $pathTo);
}
public function testRenameFileWithContext()
{
$pathFrom = sprintf('git://%s/file_0.txt', TESTS_REPO_PATH_1);
$pathTo = sprintf('git://%s/test.txt', TESTS_REPO_PATH_1);
$cntxt = stream_context_create(array(
'git' => array(
'commitMsg' => 'Hello World',
'author' => 'Luke Skywalker <skywalker@deathstar.com>'
)
));
rename($pathFrom, $pathTo, $cntxt);
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertContains('Hello World', $commit);
$this->assertContains('Luke Skywalker <skywalker@deathstar.com>', $commit);
}
public function testRmdirDirectory()
{
$c = $this->getRepository();
$c->writeFile('directory/test.txt', 'Test');
$this->assertFileExists(TESTS_REPO_PATH_1.'/directory');
$this->assertFileExists(TESTS_REPO_PATH_1.'/directory/test.txt');
$path = sprintf('git://%s/directory', TESTS_REPO_PATH_1);
rmdir($path);
$this->assertFileNotExists(TESTS_REPO_PATH_1.'/directory');
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertContains('--- a/directory/test.txt', $commit);
}
/**
* @expectedException PHPUnit_Framework_Error_Warning
*/
public function testRmdirDirectoryNonHead()
{
$c = $this->getRepository();
$c->writeFile('directory/test.txt', 'Test');
$path = sprintf('git://%s/directory#HEAD^', TESTS_REPO_PATH_1);
rmdir($path);
}
/**
* @expectedException PHPUnit_Framework_Error_Warning
*/
public function testRmdirNonExistantDirectory()
{
$path = sprintf('git://%s/directory_does_not_exist', TESTS_REPO_PATH_1);
rmdir($path);
}
/**
* @expectedException PHPUnit_Framework_Error_Warning
*/
public function testRmdirNonDirectory()
{
$path = sprintf('git://%s/file_0.txt', TESTS_REPO_PATH_1);
rmdir($path);
}
public function testRmdirDirectoryWithContext()
{
$c = $this->getRepository();
$c->writeFile('directory/test.txt', 'Test');
$this->assertFileExists(TESTS_REPO_PATH_1.'/directory');
$this->assertFileExists(TESTS_REPO_PATH_1.'/directory/test.txt');
$path = sprintf('git://%s/directory', TESTS_REPO_PATH_1);
$cntxt = stream_context_create(array(
'git' => array(
'commitMsg' => 'Hello World',
'author' => 'Luke Skywalker <skywalker@deathstar.com>'
)
));
rmdir($path, $cntxt);
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertContains('Hello World', $commit);
$this->assertContains('Luke Skywalker <skywalker@deathstar.com>', $commit);
}
public function testMkdirDirectory()
{
$path = sprintf('git://%s/directory', TESTS_REPO_PATH_1);
mkdir($path);
$this->assertFileExists(TESTS_REPO_PATH_1.'/directory');
$this->assertFileExists(TESTS_REPO_PATH_1.'/directory/.gitkeep');
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertContains('b/directory/.gitkeep', $commit);
}
/**
* @expectedException PHPUnit_Framework_Error_Warning
*/
public function testMkdirDirectoryNonHead()
{
$path = sprintf('git://%s/directory#HEAD^', TESTS_REPO_PATH_1);
mkdir($path);
}
/**
* @expectedException PHPUnit_Framework_Error_Warning
*/
public function testMkdirExistantDirectory()
{
$c = $this->getRepository();
$c->writeFile('directory/test.txt', 'Test');
$path = sprintf('git://%s/directory', TESTS_REPO_PATH_1);
mkdir($path);
}
public function testMkdirDirectoryRecursively()
{
$path = sprintf('git://%s/directory/directory', TESTS_REPO_PATH_1);
mkdir($path, 0777, true);
$this->assertFileExists(TESTS_REPO_PATH_1.'/directory');
$this->assertFileExists(TESTS_REPO_PATH_1.'/directory/directory');
$this->assertFileExists(TESTS_REPO_PATH_1.'/directory/directory/.gitkeep');
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertContains('b/directory/directory/.gitkeep', $commit);
}
/**
* @expectedException PHPUnit_Framework_Error_Warning
*/
public function testMkdirDirectoryRecursivelyFailsIfNotRequested()
{
$path = sprintf('git://%s/directory/directory', TESTS_REPO_PATH_1);
mkdir($path, 0777, false);
}
public function testMkdirDirectoryWithContext()
{
$path = sprintf('git://%s/directory', TESTS_REPO_PATH_1);
$cntxt = stream_context_create(array(
'git' => array(
'commitMsg' => 'Hello World',
'author' => 'Luke Skywalker <skywalker@deathstar.com>'
)
));
mkdir($path, 0777, false, $cntxt);
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertContains('Hello World', $commit);
$this->assertContains('Luke Skywalker <skywalker@deathstar.com>', $commit);
}
}

View File

@@ -0,0 +1,225 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace TQ\Tests\Git\StreamWrapper;
use TQ\Git\Cli\Binary;
use TQ\Git\Repository\Repository;
use TQ\Git\StreamWrapper\StreamWrapper;
use TQ\Tests\Helper;
class FileReadTest extends \PHPUnit_Framework_TestCase
{
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
Helper::removeDirectory(TESTS_TMP_PATH);
mkdir(TESTS_TMP_PATH, 0777, true);
mkdir(TESTS_REPO_PATH_1, 0777, true);
exec(sprintf('cd %s && %s init',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY
));
for ($i = 0; $i < 5; $i++) {
$file = sprintf('file_%d.txt', $i);
$path = TESTS_REPO_PATH_1.'/'.$file;
file_put_contents($path, sprintf('File %d', $i));
exec(sprintf('cd %s && %s add %s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg($file)
));
}
exec(sprintf('cd %s && %s commit --message=%s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg('Initial commit')
));
clearstatcache();
StreamWrapper::register('git', new Binary(GIT_BINARY));
}
/**
* Tears down the fixture, for example, close a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
Helper::removeDirectory(TESTS_TMP_PATH);
StreamWrapper::unregister();
}
/**
*
* @return Repository
*/
protected function getRepository()
{
return Repository::open(TESTS_REPO_PATH_1, new Binary(GIT_BINARY));
}
public function testGetContentsOfFile()
{
for ($i = 0; $i < 5; $i++) {
$file = sprintf('git://%s/file_%d.txt', TESTS_REPO_PATH_1, $i);
$content = file_get_contents($file);
$this->assertEquals(sprintf('File %d', $i), $content);
}
}
public function testReadFileByByte()
{
$filePath = sprintf('git://%s/file_0.txt', TESTS_REPO_PATH_1);
$file = fopen($filePath, 'r');
$expected = 'File 0';
$expLength = strlen($expected);
for ($i = 0; $i < $expLength; $i++) {
$this->assertEquals($i, ftell($file));
$buffer = fgetc($file);
$this->assertEquals($expected[$i], $buffer);
$this->assertEquals($i + 1, ftell($file));
}
fclose($file);
}
public function testSeekInFile()
{
$filePath = sprintf('git://%s/file_0.txt', TESTS_REPO_PATH_1);
$file = fopen($filePath, 'r');
$expected = 'File 0';
fseek($file, -1, SEEK_END);
$this->assertEquals('0', fgetc($file));
$this->assertEquals(6, ftell($file));
$this->assertTrue(feof($file));
fseek($file, 0, SEEK_SET);
$this->assertEquals('F', fgetc($file));
$this->assertEquals(1, ftell($file));
fseek($file, 3, SEEK_CUR);
$this->assertEquals(' ', fgetc($file));
$this->assertEquals(5, ftell($file));
fseek($file, -2, SEEK_CUR);
$this->assertEquals('e', fgetc($file));
$this->assertEquals(4, ftell($file));
fclose($file);
}
public function testReadFileInReverse()
{
$filePath = sprintf('git://%s/file_0.txt', TESTS_REPO_PATH_1);
$file = fopen($filePath, 'r');
$expected = '0 eliF';
$actual = '';
fseek($file, -1, SEEK_END);
while (($pos = ftell($file)) > 0) {
$actual .= fgetc($file);
fseek($file, -2, SEEK_CUR);
}
$actual .= fgetc($file);
fclose($file);
$this->assertEquals($expected, $actual);
}
public function testGetContentsOfFileWithRef()
{
$c = $this->getRepository();
$file = sprintf('git://%s/test.txt', TESTS_REPO_PATH_1);
$commit1 = $c->writeFile('test.txt', 'Test 1');
$this->assertEquals('Test 1', file_get_contents($file));
$this->assertEquals('Test 1', file_get_contents($file.'#HEAD'));
$this->assertEquals('Test 1', file_get_contents($file.'#'.$commit1));
$commit2 = $c->writeFile('test.txt', 'Test 2');
$this->assertEquals('Test 2', file_get_contents($file));
$this->assertEquals('Test 2', file_get_contents($file.'#HEAD'));
$this->assertEquals('Test 2', file_get_contents($file.'#'.$commit2));
$this->assertEquals('Test 1', file_get_contents($file.'#HEAD^'));
$this->assertEquals('Test 1', file_get_contents($file.'#'.$commit1));
$commit3 = $c->writeFile('test.txt', 'Test 3');
$this->assertEquals('Test 3', file_get_contents($file));
$this->assertEquals('Test 3', file_get_contents($file.'#HEAD'));
$this->assertEquals('Test 3', file_get_contents($file.'#'.$commit3));
$this->assertEquals('Test 2', file_get_contents($file.'#HEAD^'));
$this->assertEquals('Test 2', file_get_contents($file.'#'.$commit2));
$this->assertEquals('Test 1', file_get_contents($file.'#HEAD^^'));
$this->assertEquals('Test 1', file_get_contents($file.'#'.$commit1));
}
public function testGetContentsOfDirectory()
{
$c = $this->getRepository();
$dir = sprintf('git://%s', TESTS_REPO_PATH_1);
$this->assertEquals(Helper::normalizeNewLines("tree HEAD:
file_0.txt
file_1.txt
file_2.txt
file_3.txt
file_4.txt"), Helper::normalizeNewLines(file_get_contents($dir)));
$c->removeFile('file_0.txt');
$c->renameFile('file_1.txt', 'file_x.txt');
$this->assertEquals(Helper::normalizeNewLines("tree HEAD:
file_2.txt
file_3.txt
file_4.txt
file_x.txt"), Helper::normalizeNewLines(file_get_contents($dir)));
$this->assertEquals(Helper::normalizeNewLines("tree HEAD^:
file_1.txt
file_2.txt
file_3.txt
file_4.txt"), Helper::normalizeNewLines(file_get_contents($dir.'#HEAD^')));
$this->assertEquals(Helper::normalizeNewLines("tree HEAD^^:
file_0.txt
file_1.txt
file_2.txt
file_3.txt
file_4.txt"), Helper::normalizeNewLines(file_get_contents($dir.'#HEAD^^')));
}
}

View File

@@ -0,0 +1,293 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace TQ\Tests\Git\StreamWrapper;
use TQ\Git\Cli\Binary;
use TQ\Git\Repository\Repository;
use TQ\Git\StreamWrapper\StreamWrapper;
use TQ\Tests\Helper;
class FileWriteTest extends \PHPUnit_Framework_TestCase
{
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
Helper::removeDirectory(TESTS_TMP_PATH);
mkdir(TESTS_TMP_PATH, 0777, true);
mkdir(TESTS_REPO_PATH_1, 0777, true);
exec(sprintf('cd %s && %s init',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY
));
for ($i = 0; $i < 5; $i++) {
$file = sprintf('file_%d.txt', $i);
$path = TESTS_REPO_PATH_1.'/'.$file;
file_put_contents($path, sprintf('File %d', $i));
exec(sprintf('cd %s && %s add %s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg($file)
));
}
exec(sprintf('cd %s && %s commit --message=%s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg('Initial commit')
));
clearstatcache();
StreamWrapper::register('git', new Binary(GIT_BINARY));
}
/**
* Tears down the fixture, for example, close a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
Helper::removeDirectory(TESTS_TMP_PATH);
StreamWrapper::unregister();
}
/**
*
* @return Repository
*/
protected function getRepository()
{
return Repository::open(TESTS_REPO_PATH_1, new Binary(GIT_BINARY));
}
public function testWriteNewFile()
{
$filePath = sprintf('git://%s/test.txt', TESTS_REPO_PATH_1);
$file = fopen($filePath, 'w');
fwrite($file, 'Test');
fclose($file);
$this->assertFileExists(TESTS_REPO_PATH_1.'/test.txt');
$this->assertEquals('Test', file_get_contents(TESTS_REPO_PATH_1.'/test.txt'));
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertRegExp('~^\+\+\+ b/test.txt$~m', $commit);
$this->assertRegExp('~^\+Test$~m', $commit);
}
public function testWriteNewFileWithContext()
{
$filePath = sprintf('git://%s/test.txt', TESTS_REPO_PATH_1);
$cntxt = stream_context_create(array(
'git' => array(
'commitMsg' => 'Hello World',
'author' => 'Luke Skywalker <skywalker@deathstar.com>'
)
));
$file = fopen($filePath, 'w', false, $cntxt);
fwrite($file, 'Test');
fclose($file);
$this->assertFileExists(TESTS_REPO_PATH_1.'/test.txt');
$this->assertEquals('Test', file_get_contents(TESTS_REPO_PATH_1.'/test.txt'));
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertRegExp('~^\+\+\+ b/test.txt$~m', $commit);
$this->assertRegExp('~^\+Test$~m', $commit);
$this->assertContains('Hello World', $commit);
$this->assertContains('Luke Skywalker <skywalker@deathstar.com>', $commit);
}
public function testWriteExistingFile()
{
$filePath = sprintf('git://%s/file_0.txt', TESTS_REPO_PATH_1);
$file = fopen($filePath, 'w');
fwrite($file, 'Test');
fclose($file);
$this->assertFileExists(TESTS_REPO_PATH_1.'/file_0.txt');
$this->assertEquals('Test', file_get_contents(TESTS_REPO_PATH_1.'/file_0.txt'));
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertRegExp('~^--- a/file_0.txt$~m', $commit);
$this->assertRegExp('~^\+\+\+ b/file_0.txt$~m', $commit);
$this->assertRegExp('~^-File 0$~m', $commit);
$this->assertRegExp('~^\+Test$~m', $commit);
}
public function testAppendExistingFile()
{
$filePath = sprintf('git://%s/file_0.txt', TESTS_REPO_PATH_1);
$file = fopen($filePath, 'a');
fwrite($file, 'Test');
fclose($file);
$this->assertFileExists(TESTS_REPO_PATH_1.'/file_0.txt');
$this->assertEquals('File 0Test', file_get_contents(TESTS_REPO_PATH_1.'/file_0.txt'));
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertRegExp('~^--- a/file_0.txt$~m', $commit);
$this->assertRegExp('~^\+\+\+ b/file_0.txt$~m', $commit);
$this->assertRegExp('~^-File 0$~m', $commit);
$this->assertRegExp('~^\+File 0Test$~m', $commit);
}
public function testWriteNewFileWithX()
{
$filePath = sprintf('git://%s/test.txt', TESTS_REPO_PATH_1);
$file = fopen($filePath, 'x');
fwrite($file, 'Test');
fclose($file);
$this->assertFileExists(TESTS_REPO_PATH_1.'/test.txt');
$this->assertEquals('Test', file_get_contents(TESTS_REPO_PATH_1.'/test.txt'));
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertRegExp('~^\+\+\+ b/test.txt$~m', $commit);
$this->assertRegExp('~^\+Test$~m', $commit);
}
/**
* @expectedException PHPUnit_Framework_Error_Warning
*/
public function testWriteExistingFileWithXFails()
{
$filePath = sprintf('git://%s/file_0.txt', TESTS_REPO_PATH_1);
$file = fopen($filePath, 'x');
fwrite($file, 'Test');
}
public function testWriteNewFileWithC()
{
$filePath = sprintf('git://%s/test.txt', TESTS_REPO_PATH_1);
$file = fopen($filePath, 'c');
fwrite($file, 'Test');
fclose($file);
$this->assertFileExists(TESTS_REPO_PATH_1.'/test.txt');
$this->assertEquals('Test', file_get_contents(TESTS_REPO_PATH_1.'/test.txt'));
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertRegExp('~^\+\+\+ b/test.txt$~m', $commit);
$this->assertRegExp('~^\+Test$~m', $commit);
}
public function testWriteExistingFileWithC()
{
$filePath = sprintf('git://%s/file_0.txt', TESTS_REPO_PATH_1);
$file = fopen($filePath, 'c');
fseek($file, 0, SEEK_END);
fwrite($file, 'Test');
fclose($file);
$this->assertFileExists(TESTS_REPO_PATH_1.'/file_0.txt');
$this->assertEquals('File 0Test', file_get_contents(TESTS_REPO_PATH_1.'/file_0.txt'));
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertRegExp('~^--- a/file_0.txt$~m', $commit);
$this->assertRegExp('~^\+\+\+ b/file_0.txt$~m', $commit);
$this->assertRegExp('~^-File 0$~m', $commit);
$this->assertRegExp('~^\+File 0Test$~m', $commit);
}
public function testWriteAndReadNewFile()
{
$filePath = sprintf('git://%s/test.txt', TESTS_REPO_PATH_1);
$file = fopen($filePath, 'w+');
fwrite($file, 'Test');
fseek($file, -2, SEEK_END);
$this->assertEquals('st', fread($file, 2));
fseek($file, -2, SEEK_END);
fwrite($file, 'xt');
fclose($file);
$this->assertFileExists(TESTS_REPO_PATH_1.'/test.txt');
$this->assertEquals('Text', file_get_contents(TESTS_REPO_PATH_1.'/test.txt'));
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertRegExp('~^\+\+\+ b/test.txt$~m', $commit);
$this->assertRegExp('~^\+Text$~m', $commit);
}
public function testWriteNewFileWithFilePutContents()
{
$filePath = sprintf('git://%s/test.txt', TESTS_REPO_PATH_1);
file_put_contents($filePath, 'Test');
$this->assertFileExists(TESTS_REPO_PATH_1.'/test.txt');
$this->assertEquals('Test', file_get_contents(TESTS_REPO_PATH_1.'/test.txt'));
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertRegExp('~^\+\+\+ b/test.txt$~m', $commit);
$this->assertRegExp('~^\+Test$~m', $commit);
}
public function testWriteExistingFileWithFilePutContents()
{
$filePath = sprintf('git://%s/file_0.txt', TESTS_REPO_PATH_1);
file_put_contents($filePath, 'Test');
$this->assertFileExists(TESTS_REPO_PATH_1.'/file_0.txt');
$this->assertEquals('Test', file_get_contents(TESTS_REPO_PATH_1.'/file_0.txt'));
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertRegExp('~^--- a/file_0.txt$~m', $commit);
$this->assertRegExp('~^\+\+\+ b/file_0.txt$~m', $commit);
$this->assertRegExp('~^-File 0$~m', $commit);
$this->assertRegExp('~^\+Test$~m', $commit);
}
public function testAppendExistingFileWithFilePutContents()
{
$filePath = sprintf('git://%s/file_0.txt', TESTS_REPO_PATH_1);
file_put_contents($filePath, 'Test', FILE_APPEND);
$this->assertFileExists(TESTS_REPO_PATH_1.'/file_0.txt');
$this->assertEquals('File 0Test', file_get_contents(TESTS_REPO_PATH_1.'/file_0.txt'));
$c = $this->getRepository();
$commit = $c->showCommit($c->getCurrentCommit());
$this->assertRegExp('~^--- a/file_0.txt$~m', $commit);
$this->assertRegExp('~^\+\+\+ b/file_0.txt$~m', $commit);
$this->assertRegExp('~^-File 0$~m', $commit);
$this->assertRegExp('~^\+File 0Test$~m', $commit);
}
}

View File

@@ -0,0 +1,152 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace TQ\Tests\Git\StreamWrapper;
use TQ\Git\Cli\Binary;
use TQ\Git\Repository\Repository;
use TQ\Git\StreamWrapper\StreamWrapper;
use TQ\Tests\Helper;
class FileStatTest extends \PHPUnit_Framework_TestCase
{
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
Helper::removeDirectory(TESTS_TMP_PATH);
mkdir(TESTS_TMP_PATH, 0777, true);
mkdir(TESTS_REPO_PATH_1, 0777, true);
exec(sprintf('cd %s && %s init',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY
));
$path = TESTS_REPO_PATH_1.'/test.txt';
file_put_contents($path, 'File 1');
exec(sprintf('cd %s && %s add %s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg($path)
));
exec(sprintf('cd %s && %s commit --message=%s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg('Commit 1')
));
$path = TESTS_REPO_PATH_1.'/directory';
mkdir($path, 0777);
file_put_contents($path.'/test.txt', 'Directory File 1');
exec(sprintf('cd %s && %s add %s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg($path)
));
exec(sprintf('cd %s && %s commit --message=%s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg('Commit 2')
));
$path = TESTS_REPO_PATH_1.'/test.txt';
file_put_contents($path, 'File 1 New');
exec(sprintf('cd %s && %s add %s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg($path)
));
exec(sprintf('cd %s && %s commit --message=%s',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY,
escapeshellarg('Commit 3')
));
clearstatcache();
StreamWrapper::register('git', new Binary(GIT_BINARY));
}
/**
* Tears down the fixture, for example, close a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
Helper::removeDirectory(TESTS_TMP_PATH);
StreamWrapper::unregister();
}
/**
*
* @return Repository
*/
protected function getRepository()
{
return Repository::open(TESTS_REPO_PATH_1, new Binary(GIT_BINARY));
}
public function testUrlStatFileWorkingDirectory()
{
$filePath = sprintf('git://%s/test.txt', TESTS_REPO_PATH_1);
$stat = stat($filePath);
$this->assertEquals(26, count($stat));
$this->assertEquals(0100000, $stat['mode'] & 0100000);
$this->assertEquals(10, $stat['size']);
}
public function testUrlStatDirWorkingDirectory()
{
$dirPath = sprintf('git://%s/directory', TESTS_REPO_PATH_1);
$stat = stat($dirPath);
$this->assertEquals(26, count($stat));
$this->assertEquals(0040000, $stat['mode'] & 0040000);
}
public function testUrlStatFileHistory()
{
$filePath = sprintf('git://%s/test.txt#HEAD^', TESTS_REPO_PATH_1);
$stat = stat($filePath);
$this->assertEquals(26, count($stat));
$this->assertEquals(0100000, $stat['mode'] & 0100000);
$this->assertEquals(6, $stat['size']);
}
public function testUrlStatDirHistory()
{
$dirPath = sprintf('git://%s/directory#HEAD^', TESTS_REPO_PATH_1);
$stat = stat($dirPath);
$this->assertEquals(26, count($stat));
$this->assertEquals(0040000, $stat['mode'] & 0040000);
}
}

View File

@@ -0,0 +1,136 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace TQ\Tests\Git\StreamWrapper;
use TQ\Git\Cli\Binary;
use TQ\Git\Repository\Repository;
use TQ\Git\StreamWrapper\StreamWrapper;
use TQ\Tests\Helper;
class StatusReadTest extends \PHPUnit_Framework_TestCase
{
/**
* Sets up the fixture, for example, open a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
Helper::removeDirectory(TESTS_TMP_PATH);
mkdir(TESTS_TMP_PATH, 0777, true);
mkdir(TESTS_REPO_PATH_1, 0777, true);
exec(sprintf('cd %s && %s init',
escapeshellarg(TESTS_REPO_PATH_1),
GIT_BINARY
));
clearstatcache();
StreamWrapper::register('git', new Binary(GIT_BINARY));
}
/**
* Tears down the fixture, for example, close a network connection.
* This method is called after a test is executed.
*/
protected function tearDown()
{
Helper::removeDirectory(TESTS_TMP_PATH);
StreamWrapper::unregister();
}
/**
*
* @return Repository
*/
protected function getRepository()
{
return Repository::open(TESTS_REPO_PATH_1, new Binary(GIT_BINARY));
}
public function testReadCommit() {
$c = $this->getRepository();
$commits = array();
for ($i = 0; $i < 5; $i++) {
$commits[] = $c->writeFile(sprintf('test_%d.txt', $i), sprintf('This is file %d', $i));
}
foreach ($commits as $c => $commitHash) {
$commitUrl = sprintf('git://%s?commit&ref=%s', TESTS_REPO_PATH_1, $commitHash);
$content = file_get_contents($commitUrl);
$this->assertStringStartsWith('commit '.$commitHash, $content);
$this->assertContains(sprintf('+++ b/test_%d.txt', $c), $content);
$this->assertContains(sprintf('+This is file %d', $c), $content);
}
}
public function testReadLog() {
$c = $this->getRepository();
$commits = array();
for ($i = 0; $i < 5; $i++) {
$commits[] = $c->writeFile(sprintf('test_%d.txt', $i), sprintf('This is file %d', $i));
}
$logUrl = sprintf('git://%s?log', TESTS_REPO_PATH_1);
$log = file_get_contents($logUrl);
foreach ($commits as $c => $commitHash) {
$this->assertContains('commit '.$commitHash, $log);
}
$logUrl = sprintf('git://%s?log&limit=%d', TESTS_REPO_PATH_1, 1);
$log = file_get_contents($logUrl);
foreach ($commits as $c => $commitHash) {
if ($c == count($commits) - 1) {
$this->assertContains('commit '.$commitHash, $log);
} else {
$this->assertNotContains('commit '.$commitHash, $log);
}
}
$logUrl = sprintf('git://%s?log&limit=%d', TESTS_REPO_PATH_1, 2);
$log = file_get_contents($logUrl);
foreach ($commits as $c => $commitHash) {
if ($c >= count($commits) - 2) {
$this->assertContains('commit '.$commitHash, $log);
} else {
$this->assertNotContains('commit '.$commitHash, $log);
}
}
$logUrl = sprintf('git://%s?log&limit=%d&skip=%d', TESTS_REPO_PATH_1, 2, 1);
$log = file_get_contents($logUrl);
foreach ($commits as $c => $commitHash) {
if (($c >= count($commits) - 3) && ($c < count($commits) - 1)) {
$this->assertContains('commit '.$commitHash, $log);
} else {
$this->assertNotContains('commit '.$commitHash, $log);
}
}
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace TQ\Tests;
class Helper
{
public static function removeDirectory($path)
{
clearstatcache();
if (!file_exists($path)) {
return;
}
if (!is_dir($path)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a directory', $path));
}
$dirIt = new \RecursiveDirectoryIterator($path,
\RecursiveDirectoryIterator::SKIP_DOTS
| \RecursiveDirectoryIterator::KEY_AS_PATHNAME
| \RecursiveDirectoryIterator::CURRENT_AS_FILEINFO
);
$it = new \RecursiveIteratorIterator($dirIt,
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($it as $p => $f) {
if ($f->isDir()) {
rmdir($p);
} else if ($f->isFile()) {
chmod($p, 0777);
unlink($p);
}
}
rmdir($path);
}
public static function normalizeDirectorySeparator($path)
{
return str_replace(DIRECTORY_SEPARATOR, '/', $path);
}
public static function normalizeNewLines($string)
{
return str_replace("\r\n", "\n", $string);
}
public static function normalizeEscapeShellArg($command)
{
return str_replace("'", '"', $command);
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* Copyright (C) 2011 by TEQneers GmbH & Co. KG
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
require_once __DIR__.'/TQ/Tests/Helper.php';
if (file_exists($file = __DIR__.'/../autoload.php')) {
require_once $file;
} elseif (file_exists($file = __DIR__.'/../autoload.php.dist')) {
require_once $file;
}
use TQ\Tests\Helper;
define('PROJECT_PATH', Helper::normalizeDirectorySeparator(dirname(__DIR__)));
define('SOURCE_PATH', PROJECT_PATH.'/src');
define('TESTS_PATH', Helper::normalizeDirectorySeparator(__DIR__));
if (defined('TEST_REPO_PATH') && is_string(TEST_REPO_PATH)) {
define('TESTS_TMP_PATH', Helper::normalizeDirectorySeparator(TEST_REPO_PATH).'/_tq_git_streamwrapper_tests');
} else {
define('TESTS_TMP_PATH', Helper::normalizeDirectorySeparator(sys_get_temp_dir()).'/_tq_git_streamwrapper_tests');
}
define('TESTS_REPO_PATH_1', TESTS_TMP_PATH.'/repo1');
define('TESTS_REPO_PATH_2', TESTS_TMP_PATH.'/repo2');
define('TESTS_REPO_PATH_3', TESTS_TMP_PATH.'/repo3');