On Sunday we released Git Stream Wrapper for PHP 1.0.0-beta with three interesting new features following extensive refactoring. The component is still public API compatible to the old 0.* releases but the internals have been changed drastically so custom extensions might be broken now.

The first new feature allows you to register repository paths on the stream wrapper to shorten the paths required to access files in these repositories. That’s especially useful if you only use one or two different repositories.

<?php
use TQGitStreamWrapperStreamWrapper;
use TQGitRepositoryRepository;

StreamWrapper::register('git', '/usr/bin/git');
StreamWrapper::getRepositoryRegistry()->addRepositories(
    array(
        'repo1' => Repository::open('/path/to/repository/1', '/usr/bin/git', false),
        'repo2' => Repository::open('/path/to/repository/2', '/usr/bin/git', false),
    )
);
$content1 = file_get_contents('git://repo1/file_0.txt');
$content2 = file_get_contents('git://repo2/file_0.txt');

Instead of using the full path git:///path/to/repository/1/file_0.txt you can now shorten that to git://repo1/file_0.txt. This makes code more readable and allows you to configure repository paths in one central location.

For those of you who know Gaufrette we also added a Gaufrette adapter that links Gaufrette’s filesystem to a Git repository. For those of you who don’t know Gaufrette: you should definitively take a look. Gaufrette is a filesystem abstraction layer that provides a ton of so called adapters that allow you to use various storage systems transparently in your code. They have adapters available for Amazon S3, Azure, Databases, Dropbox, GridFS, (S)FTP, etc. and there is a special in-memory adapter that’s especially handy during testing. Git Stream Wrapper for PHP now provides its own Gaufrette adapter.

<?php
use GaufretteFilesystem;
use GaufretteStreamWrapper;
use TQGitRepositoryRepository;
use TQVcsGaufretteAdapter as GitAdapter;

$repository = Repository::open('/path/to/repository/1', '/usr/bin/git', false);
$adapter    = new GitAdapter($repository);
$filesystem = new Filesystem($adapter);

$content = $filesystem->read('file_0.txt');
$content = 'Hello I am the new content';
$filesystem->write('file_0.txt', $content);

File changes, removals and additions are automatically committed to the underlying repository. You can even use the Gaufrette stream wrapper.

$map = StreamWrapper::getFilesystemMap();
$map->set('repo1', $filesystem);
StreamWrapper::register();
echo file_get_contents('gaufrette://repo1/file_0.txt');

Last feature added recently is perhaps the most exciting one – even if it leaves the library name “Git Stream Wrapper for PHP” outdated. As a mere proof of concept for the underlying component architecture we added SVN support as well. So actually it’s now the “Git/SVN Stream Wrapper for PHP”. SVN support is transparently integrated into the library allowing you to chose the most appropriate version control technology for you project. Just as with the Git support you need Subversion binaries installed on the machine running the PHP code. Due to some restrictions when comparing the Git binary to the SVN binary we could not provide support for all the features you can use with the Git component. Especially the various Subversion versions posed a great challenge. On my Mac for example I’m running a localized (German) version of Subversion 1.8.x (via Macports) or the 1.7.x version (non-localized) Apple shipped with Mac OS X, but Travis CI (which runs the unit tests on PHP 5.3, 5.4 and 5.5) uses Ubuntu 12.04 and their Subversion 1.6.* (yes, that’s still the one with .svn directories all over the place). Git on the other hand did not show that kind of incompatibility between its various versions – just to say.

<?php
use TQSvnRepositoryRepository;
// open an already initialized repository
$svn = Repository::open('/path/to/your/repository', '/usr/bin/svn');

// open repository
$svn = Repository::open('/path/to/your/repository', '/usr/bin/svn');

// get status of working directory
$status = $svn->getStatus();
// are there uncommitted changes in the working directory
$isDirty = $svn->isDirty();

// retrieve the commit log limited to $limit entries skipping the first $skip
$log = $svn->getLog($limit, $skip);

// retrieve the second to last commit
$commit = $svn->showCommit($svn->getCurrentCommit() - 1);

// list the directory contents two commits before
$list = $svn->listDirectory('.', $svn->getCurrentCommit() - 2);

// show contents of file $file at commit 322...
$contents = $svn->showFile($file, 322);

// write a file and commit the changes
$commit = $svn->writeFile('test.txt', 'Test', 'Added test.txt');

// remove multiple files
$commit = $svn->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 = $svn->transactional(function(TQVcsRepositoryTransaction $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 exception 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 stream wrapper works as well the same way it works with Git. Please note that it’s currently not possible to use the same stream wrapper for both Git and SVN access. In that case you’d have to register both stream wrappers.

Just a word of warning: the SVN component is not production tested yet (unlike the Git component) so please be aware of bugs, incompatibilities and other problems. Please feel free to open issues on the project’s GitHub page or, even better, provide patches and/or pull requests to fix these issues.

Please read the README document for more information – especially on the changes needed when setting up the unit tests.

Final note: we also tried to run Travis CI tests on HHVM (HipHop Virtual Machine for PHP) but the tests are failing. The reason seems to be that HHVM does not support stream_context_get_options() at the moment.