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 ;
/**
* @ 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-06-20 09:46:59 +00:00
$command = 'git clone %s %s && cd %2$s && git remote add composer %1$s && git fetch composer' ;
$this -> io -> write ( " Cloning " . $ref );
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 );
2012-06-20 09:46:59 +00:00
2012-06-20 10:05:18 +00:00
$this -> updateToCommit ( $path , $ref , $package -> getPrettyVersion (), $package -> getReleaseDate ());
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-06-20 09:46:59 +00:00
$this -> io -> write ( " Checking out " . $ref );
$command = 'cd %s && git remote set-url composer %s && git fetch composer && git fetch --tags composer' ;
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-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 ());
2012-06-20 10:05:18 +00:00
$this -> updateToCommit ( $path , $ref , $target -> getPrettyVersion (), $target -> getReleaseDate ());
2012-06-20 09:46:59 +00:00
}
protected function updateToCommit ( $path , $reference , $branch , $date )
{
$template = 'git checkout %s && git reset --hard %1$s' ;
$command = sprintf ( $template , escapeshellarg ( $reference ));
if ( 0 === $this -> process -> execute ( $command , $output , $path )) {
return ;
}
// reference was not found (prints "fatal: reference is not a tree: $ref")
2012-06-20 10:05:18 +00:00
if ( $date && false !== strpos ( $this -> process -> getErrorOutput (), $reference )) {
2012-06-20 09:46:59 +00:00
$branch = preg_replace ( '{(?:^dev-|(?:\.x)?-dev$)}i' , '' , $branch );
2012-06-20 10:05:18 +00:00
$date = $date -> format ( 'U' );
2012-06-20 09:46:59 +00:00
$command = 'git branch -r' ;
if ( 1 === $this -> process -> execute ( $command , $output , $path )) {
throw new \RuntimeException ( 'Failed to execute ' . $command . " \n \n " . $this -> process -> getErrorOutput ());
}
$guessTemplate = 'git log --until=%s --date=raw -n1 --pretty=%%H %s' ;
foreach ( $this -> process -> splitLines ( $output ) as $line ) {
if ( preg_match ( '{^composer/' . preg_quote ( $branch ) . '(?:\.x)?$}i' , trim ( $line ))) {
if ( 0 === $this -> process -> execute ( sprintf ( $guessTemplate , $date , escapeshellarg ( trim ( $line ))), $output , $path )) {
$newReference = trim ( $output );
}
break ;
}
}
if ( empty ( $newReference )) {
if ( 0 !== $this -> process -> execute ( sprintf ( $guessTemplate , $date , '--all' ), $output , $path )) {
throw new \RuntimeException ( 'Failed to execute ' . $command . " \n \n " . $this -> process -> getErrorOutput ());
}
$newReference = trim ( $output );
}
$command = sprintf ( $template , escapeshellarg ( $newReference ));
if ( 0 === $this -> process -> execute ( $command , $output , $path )) {
$this -> io -> write ( ' ' . $reference . ' is gone (history was rewritten?), recovered by checking out ' . $newReference );
return ;
}
}
throw new \RuntimeException ( 'Failed to execute ' . $command . " \n \n " . $this -> process -> getErrorOutput ());
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 .
*
2012-05-22 10:07:08 +00:00
* @ 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 )
2012-03-10 16:49:08 +00:00
* @ 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' );
2012-05-18 12:41:57 +00:00
$messages = array ();
2012-03-10 16:49:08 +00:00
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 ;
}
2012-05-18 12:41:57 +00:00
$messages [] = '- ' . $url . " \n " . preg_replace ( '#^#m' , ' ' , $this -> process -> getErrorOutput ());
2012-03-10 16:49:08 +00:00
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
2012-05-18 12:41:57 +00:00
$this -> throwException ( 'Failed to clone ' . $url . ' via git, https and http protocols, aborting.' . " \n \n " . implode ( " \n " , $messages ), $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 ;
}
2012-05-12 16:24:07 +00:00
if ( null !== $path ) {
$this -> filesystem -> removeDirectory ( $path );
}
2012-05-06 15:19:30 +00:00
$retrying = true ;
} while ( -- $retries );
}
2012-05-12 16:24:07 +00:00
if ( null !== $path ) {
$this -> filesystem -> removeDirectory ( $path );
}
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
}