2011-04-17 22:14:44 +00:00
< ? php
/*
* This file is part of Composer .
*
* ( c ) Nils Adermann < naderman @ naderman . de >
* Jordi Boggiano < j . boggiano @ seld . be >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Composer\Downloader ;
use Composer\Package\PackageInterface ;
2012-01-18 07:56:35 +00:00
use Composer\Util\ProcessExecutor ;
2011-04-17 22:14:44 +00:00
/**
* @ author Jordi Boggiano < j . boggiano @ seld . be >
*/
2012-01-22 20:14:56 +00:00
class GitDownloader extends VcsDownloader
2011-04-17 22:14:44 +00:00
{
2011-09-25 10:39:08 +00:00
/**
* { @ inheritDoc }
*/
2012-01-22 20:14:56 +00:00
public function doDownload ( PackageInterface $package , $path )
2011-09-25 10:39:08 +00:00
{
2012-04-04 15:11:50 +00:00
$ref = $package -> getSourceReference ();
2012-04-17 18:02:19 +00:00
$command = 'git clone %s %s && cd %2$s && git checkout %3$s && git reset --hard %3$s && git remote add composer %1$s' ;
2012-01-22 20:14:56 +00:00
$this -> io -> write ( " Cloning " . $package -> getSourceReference ());
2012-03-06 23:58:37 +00:00
2012-03-10 16:49:08 +00:00
$commandCallable = function ( $url ) use ( $ref , $path , $command ) {
2012-04-04 15:11:50 +00:00
return sprintf ( $command , escapeshellarg ( $url ), escapeshellarg ( $path ), escapeshellarg ( $ref ));
2012-03-10 16:49:08 +00:00
};
$this -> runCommand ( $commandCallable , $package -> getSourceUrl (), $path );
2012-04-03 17:49:57 +00:00
$this -> setPushUrl ( $package , $path );
2011-09-28 22:48:17 +00:00
}
2011-09-25 10:39:08 +00:00
2011-09-28 22:48:17 +00:00
/**
* { @ inheritDoc }
*/
2012-01-22 20:14:56 +00:00
public function doUpdate ( PackageInterface $initial , PackageInterface $target , $path )
2011-04-17 22:14:44 +00:00
{
2012-04-04 15:11:50 +00:00
$ref = $target -> getSourceReference ();
2012-01-22 20:14:56 +00:00
$this -> io -> write ( " Checking out " . $target -> getSourceReference ());
2012-04-17 18:02:19 +00:00
$command = 'cd %s && git remote set-url composer %s && git fetch composer && git fetch --tags composer && git checkout %3$s && git reset --hard %3$s' ;
2012-03-10 16:49:08 +00:00
2012-05-06 15:19:30 +00:00
// capture username/password from github URL if there is one
$this -> process -> execute ( sprintf ( 'cd %s && git remote -v' , escapeshellarg ( $path )), $output );
if ( preg_match ( '{^composer\s+https://(.+):(.+)@github.com/}im' , $output , $match )) {
$this -> io -> setAuthorization ( 'github.com' , $match [ 1 ], $match [ 2 ]);
}
2012-04-18 14:05:23 +00:00
// TODO: BC for the composer remote that didn't exist, to be remove after May 18th.
$this -> process -> execute ( sprintf ( 'cd %s && git remote add composer %s' , escapeshellarg ( $path ), escapeshellarg ( $initial -> getSourceUrl ())), $ignoredOutput );
2012-03-10 16:49:08 +00:00
$commandCallable = function ( $url ) use ( $ref , $path , $command ) {
2012-04-04 15:11:50 +00:00
return sprintf ( $command , escapeshellarg ( $path ), escapeshellarg ( $url ), escapeshellarg ( $ref ));
2012-03-10 16:49:08 +00:00
};
$this -> runCommand ( $commandCallable , $target -> getSourceUrl ());
2011-04-17 22:14:44 +00:00
}
2011-09-25 10:39:08 +00:00
/**
* { @ inheritDoc }
*/
2012-01-22 20:14:56 +00:00
protected function enforceCleanDirectory ( $path )
2011-04-17 22:14:44 +00:00
{
2012-04-27 09:23:34 +00:00
$command = sprintf ( 'cd %s && git status --porcelain --untracked-files=no' , escapeshellarg ( $path ));
2012-02-20 23:02:34 +00:00
if ( 0 !== $this -> process -> execute ( $command , $output )) {
2012-02-29 11:05:25 +00:00
throw new \RuntimeException ( 'Failed to execute ' . $command . " \n \n " . $this -> process -> getErrorOutput ());
2012-02-20 23:02:34 +00:00
}
2012-01-22 19:08:20 +00:00
if ( trim ( $output )) {
2012-03-06 17:21:45 +00:00
throw new \RuntimeException ( 'Source directory ' . $path . ' has uncommitted changes' );
2011-09-28 22:48:17 +00:00
}
2011-04-17 22:14:44 +00:00
}
2012-03-10 16:49:08 +00:00
/**
* Runs a command doing attempts for each protocol supported by github .
*
* @ param callable $commandCallable A callable building the command for the given url
* @ param string $url
* @ param string $path The directory to remove for each attempt ( null if not needed )
* @ throws \RuntimeException
*/
protected function runCommand ( $commandCallable , $url , $path = null )
{
2012-04-04 07:54:27 +00:00
$handler = array ( $this , 'outputHandler' );
2012-03-10 16:49:08 +00:00
// github, autoswitch protocols
if ( preg_match ( '{^(?:https?|git)(://github.com/.*)}' , $url , $match )) {
$protocols = array ( 'git' , 'https' , 'http' );
foreach ( $protocols as $protocol ) {
2012-04-04 15:11:50 +00:00
$url = $protocol . $match [ 1 ];
2012-04-04 07:54:27 +00:00
if ( 0 === $this -> process -> execute ( call_user_func ( $commandCallable , $url ), $handler )) {
2012-03-10 16:49:08 +00:00
return ;
}
if ( null !== $path ) {
$this -> filesystem -> removeDirectory ( $path );
}
}
2012-04-05 15:30:50 +00:00
2012-04-06 11:21:04 +00:00
// failed to checkout, first check git accessibility
$this -> throwException ( 'Failed to clone ' . $url . ' via git, https and http protocols, aborting.' . " \n \n " . $this -> process -> getErrorOutput (), $url );
2012-04-05 15:26:15 +00:00
}
2012-03-10 16:49:08 +00:00
$command = call_user_func ( $commandCallable , $url );
2012-04-04 07:54:27 +00:00
if ( 0 !== $this -> process -> execute ( $command , $handler )) {
2012-05-06 15:19:30 +00:00
if ( preg_match ( '{^git@github.com:(.+?)\.git$}i' , $url , $match ) && $this -> io -> isInteractive ()) {
// private repository without git access, try https with auth
$retries = 3 ;
$retrying = false ;
do {
if ( $retrying ) {
$this -> io -> write ( 'Invalid credentials' );
}
if ( ! $this -> io -> hasAuthorization ( 'github.com' ) || $retrying ) {
$username = $this -> io -> ask ( 'Username: ' );
$password = $this -> io -> askAndHideAnswer ( 'Password: ' );
$this -> io -> setAuthorization ( 'github.com' , $username , $password );
}
$auth = $this -> io -> getAuthorization ( 'github.com' );
$url = 'https://' . $auth [ 'username' ] . ':' . $auth [ 'password' ] . '@github.com/' . $match [ 1 ] . '.git' ;
$command = call_user_func ( $commandCallable , $url );
if ( 0 === $this -> process -> execute ( $command , $handler )) {
return ;
}
$retrying = true ;
} while ( -- $retries );
}
2012-04-06 11:21:04 +00:00
$this -> throwException ( 'Failed to execute ' . $command . " \n \n " . $this -> process -> getErrorOutput (), $url );
2012-03-10 16:49:08 +00:00
}
}
2012-04-03 17:49:57 +00:00
2012-04-04 07:54:27 +00:00
public function outputHandler ( $type , $buffer )
{
if ( $type !== 'out' ) {
return ;
}
if ( $this -> io -> isVerbose ()) {
$this -> io -> write ( $buffer , false );
}
}
2012-04-06 11:21:04 +00:00
protected function throwException ( $message , $url )
{
if ( 0 !== $this -> process -> execute ( 'git --version' , $ignoredOutput )) {
throw new \RuntimeException ( 'Failed to clone ' . $url . ', git was not found, check that it is installed and in your PATH env.' . " \n \n " . $this -> process -> getErrorOutput ());
}
throw new \RuntimeException ( $message );
}
2012-04-03 17:49:57 +00:00
protected function setPushUrl ( PackageInterface $package , $path )
{
// set push url for github projects
if ( preg_match ( '{^(?:https?|git)://github.com/([^/]+)/([^/]+?)(?:\.git)?$}' , $package -> getSourceUrl (), $match )) {
$pushUrl = 'git@github.com:' . $match [ 1 ] . '/' . $match [ 2 ] . '.git' ;
2012-04-04 15:11:10 +00:00
$cmd = sprintf ( 'git remote set-url --push origin %s' , escapeshellarg ( $pushUrl ));
$this -> process -> execute ( $cmd , $ignoredOutput , $path );
2012-04-03 17:49:57 +00:00
}
}
2011-09-17 13:12:45 +00:00
}