PageRenderTime 57ms CodeModel.GetById 12ms app.highlight 36ms RepoModel.GetById 2ms app.codeStats 0ms

/gespac/config/PEAR/PEAR/Command/Package.php

http://gespac.googlecode.com/
PHP | 1119 lines | 931 code | 126 blank | 62 comment | 151 complexity | 942478fbb01914eec4e86e8f8f6b5f81 MD5 | raw file
   1<?php
   2/**
   3 * PEAR_Command_Package (package, package-validate, cvsdiff, cvstag, package-dependencies,
   4 * sign, makerpm, convert commands)
   5 *
   6 * PHP versions 4 and 5
   7 *
   8 * @category   pear
   9 * @package    PEAR
  10 * @author     Stig Bakken <ssb@php.net>
  11 * @author     Martin Jansen <mj@php.net>
  12 * @author     Greg Beaver <cellog@php.net>
  13 * @copyright  1997-2009 The Authors
  14 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
  15 * @version    CVS: $Id: Package.php 288113 2009-09-06 21:11:55Z dufuz $
  16 * @link       http://pear.php.net/package/PEAR
  17 * @since      File available since Release 0.1
  18 */
  19
  20/**
  21 * base class
  22 */
  23require_once 'PEAR/Command/Common.php';
  24
  25/**
  26 * PEAR commands for login/logout
  27 *
  28 * @category   pear
  29 * @package    PEAR
  30 * @author     Stig Bakken <ssb@php.net>
  31 * @author     Martin Jansen <mj@php.net>
  32 * @author     Greg Beaver <cellog@php.net>
  33 * @copyright  1997-2009 The Authors
  34 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
  35 * @version    Release: @package_version@
  36 * @link       http://pear.php.net/package/PEAR
  37 * @since      Class available since Release 0.1
  38 */
  39
  40class PEAR_Command_Package extends PEAR_Command_Common
  41{
  42    var $commands = array(
  43        'package' => array(
  44            'summary' => 'Build Package',
  45            'function' => 'doPackage',
  46            'shortcut' => 'p',
  47            'options' => array(
  48                'nocompress' => array(
  49                    'shortopt' => 'Z',
  50                    'doc' => 'Do not gzip the package file'
  51                    ),
  52                'showname' => array(
  53                    'shortopt' => 'n',
  54                    'doc' => 'Print the name of the packaged file.',
  55                    ),
  56                ),
  57            'doc' => '[descfile] [descfile2]
  58Creates a PEAR package from its description file (usually called
  59package.xml).  If a second packagefile is passed in, then
  60the packager will check to make sure that one is a package.xml
  61version 1.0, and the other is a package.xml version 2.0.  The
  62package.xml version 1.0 will be saved as "package.xml" in the archive,
  63and the other as "package2.xml" in the archive"
  64'
  65            ),
  66        'package-validate' => array(
  67            'summary' => 'Validate Package Consistency',
  68            'function' => 'doPackageValidate',
  69            'shortcut' => 'pv',
  70            'options' => array(),
  71            'doc' => '
  72',
  73            ),
  74        'cvsdiff' => array(
  75            'summary' => 'Run a "cvs diff" for all files in a package',
  76            'function' => 'doCvsDiff',
  77            'shortcut' => 'cd',
  78            'options' => array(
  79                'quiet' => array(
  80                    'shortopt' => 'q',
  81                    'doc' => 'Be quiet',
  82                    ),
  83                'reallyquiet' => array(
  84                    'shortopt' => 'Q',
  85                    'doc' => 'Be really quiet',
  86                    ),
  87                'date' => array(
  88                    'shortopt' => 'D',
  89                    'doc' => 'Diff against revision of DATE',
  90                    'arg' => 'DATE',
  91                    ),
  92                'release' => array(
  93                    'shortopt' => 'R',
  94                    'doc' => 'Diff against tag for package release REL',
  95                    'arg' => 'REL',
  96                    ),
  97                'revision' => array(
  98                    'shortopt' => 'r',
  99                    'doc' => 'Diff against revision REV',
 100                    'arg' => 'REV',
 101                    ),
 102                'context' => array(
 103                    'shortopt' => 'c',
 104                    'doc' => 'Generate context diff',
 105                    ),
 106                'unified' => array(
 107                    'shortopt' => 'u',
 108                    'doc' => 'Generate unified diff',
 109                    ),
 110                'ignore-case' => array(
 111                    'shortopt' => 'i',
 112                    'doc' => 'Ignore case, consider upper- and lower-case letters equivalent',
 113                    ),
 114                'ignore-whitespace' => array(
 115                    'shortopt' => 'b',
 116                    'doc' => 'Ignore changes in amount of white space',
 117                    ),
 118                'ignore-blank-lines' => array(
 119                    'shortopt' => 'B',
 120                    'doc' => 'Ignore changes that insert or delete blank lines',
 121                    ),
 122                'brief' => array(
 123                    'doc' => 'Report only whether the files differ, no details',
 124                    ),
 125                'dry-run' => array(
 126                    'shortopt' => 'n',
 127                    'doc' => 'Don\'t do anything, just pretend',
 128                    ),
 129                ),
 130            'doc' => '<package.xml>
 131Compares all the files in a package.  Without any options, this
 132command will compare the current code with the last checked-in code.
 133Using the -r or -R option you may compare the current code with that
 134of a specific release.
 135',
 136            ),
 137         'svntag' => array(
 138             'summary' => 'Set SVN Release Tag',
 139             'function' => 'doSvnTag',
 140             'shortcut' => 'sv',
 141             'options' => array(
 142                 'quiet' => array(
 143                     'shortopt' => 'q',
 144                     'doc' => 'Be quiet',
 145                     ),
 146                 'slide' => array(
 147                     'shortopt' => 'F',
 148                     'doc' => 'Move (slide) tag if it exists',
 149                     ),
 150                 'delete' => array(
 151                     'shortopt' => 'd',
 152                     'doc' => 'Remove tag',
 153                     ),
 154                 'dry-run' => array(
 155                     'shortopt' => 'n',
 156                     'doc' => 'Don\'t do anything, just pretend',
 157                     ),
 158                 ),
 159             'doc' => '<package.xml> [files...]
 160 Sets a SVN tag on all files in a package.  Use this command after you have
 161 packaged a distribution tarball with the "package" command to tag what
 162 revisions of what files were in that release.  If need to fix something
 163 after running cvstag once, but before the tarball is released to the public,
 164 use the "slide" option to move the release tag.
 165
 166 to include files (such as a second package.xml, or tests not included in the
 167 release), pass them as additional parameters.
 168 ',
 169             ),
 170        'cvstag' => array(
 171            'summary' => 'Set CVS Release Tag',
 172            'function' => 'doCvsTag',
 173            'shortcut' => 'ct',
 174            'options' => array(
 175                'quiet' => array(
 176                    'shortopt' => 'q',
 177                    'doc' => 'Be quiet',
 178                    ),
 179                'reallyquiet' => array(
 180                    'shortopt' => 'Q',
 181                    'doc' => 'Be really quiet',
 182                    ),
 183                'slide' => array(
 184                    'shortopt' => 'F',
 185                    'doc' => 'Move (slide) tag if it exists',
 186                    ),
 187                'delete' => array(
 188                    'shortopt' => 'd',
 189                    'doc' => 'Remove tag',
 190                    ),
 191                'dry-run' => array(
 192                    'shortopt' => 'n',
 193                    'doc' => 'Don\'t do anything, just pretend',
 194                    ),
 195                ),
 196            'doc' => '<package.xml> [files...]
 197Sets a CVS tag on all files in a package.  Use this command after you have
 198packaged a distribution tarball with the "package" command to tag what
 199revisions of what files were in that release.  If need to fix something
 200after running cvstag once, but before the tarball is released to the public,
 201use the "slide" option to move the release tag.
 202
 203to include files (such as a second package.xml, or tests not included in the
 204release), pass them as additional parameters.
 205',
 206            ),
 207        'package-dependencies' => array(
 208            'summary' => 'Show package dependencies',
 209            'function' => 'doPackageDependencies',
 210            'shortcut' => 'pd',
 211            'options' => array(),
 212            'doc' => '<package-file> or <package.xml> or <install-package-name>
 213List all dependencies the package has.
 214Can take a tgz / tar file, package.xml or a package name of an installed package.'
 215            ),
 216        'sign' => array(
 217            'summary' => 'Sign a package distribution file',
 218            'function' => 'doSign',
 219            'shortcut' => 'si',
 220            'options' => array(
 221                'verbose' => array(
 222                    'shortopt' => 'v',
 223                    'doc' => 'Display GnuPG output',
 224                    ),
 225            ),
 226            'doc' => '<package-file>
 227Signs a package distribution (.tar or .tgz) file with GnuPG.',
 228            ),
 229        'makerpm' => array(
 230            'summary' => 'Builds an RPM spec file from a PEAR package',
 231            'function' => 'doMakeRPM',
 232            'shortcut' => 'rpm',
 233            'options' => array(
 234                'spec-template' => array(
 235                    'shortopt' => 't',
 236                    'arg' => 'FILE',
 237                    'doc' => 'Use FILE as RPM spec file template'
 238                    ),
 239                'rpm-pkgname' => array(
 240                    'shortopt' => 'p',
 241                    'arg' => 'FORMAT',
 242                    'doc' => 'Use FORMAT as format string for RPM package name, %s is replaced
 243by the PEAR package name, defaults to "PEAR::%s".',
 244                    ),
 245                ),
 246            'doc' => '<package-file>
 247
 248Creates an RPM .spec file for wrapping a PEAR package inside an RPM
 249package.  Intended to be used from the SPECS directory, with the PEAR
 250package tarball in the SOURCES directory:
 251
 252$ pear makerpm ../SOURCES/Net_Socket-1.0.tgz
 253Wrote RPM spec file PEAR::Net_Geo-1.0.spec
 254$ rpm -bb PEAR::Net_Socket-1.0.spec
 255...
 256Wrote: /usr/src/redhat/RPMS/i386/PEAR::Net_Socket-1.0-1.i386.rpm
 257',
 258            ),
 259        'convert' => array(
 260            'summary' => 'Convert a package.xml 1.0 to package.xml 2.0 format',
 261            'function' => 'doConvert',
 262            'shortcut' => 'c2',
 263            'options' => array(
 264                'flat' => array(
 265                    'shortopt' => 'f',
 266                    'doc' => 'do not beautify the filelist.',
 267                    ),
 268                ),
 269            'doc' => '[descfile] [descfile2]
 270Converts a package.xml in 1.0 format into a package.xml
 271in 2.0 format.  The new file will be named package2.xml by default,
 272and package.xml will be used as the old file by default.
 273This is not the most intelligent conversion, and should only be
 274used for automated conversion or learning the format.
 275'
 276            ),
 277        );
 278
 279    var $output;
 280
 281    /**
 282     * PEAR_Command_Package constructor.
 283     *
 284     * @access public
 285     */
 286    function PEAR_Command_Package(&$ui, &$config)
 287    {
 288        parent::PEAR_Command_Common($ui, $config);
 289    }
 290
 291    function _displayValidationResults($err, $warn, $strict = false)
 292    {
 293        foreach ($err as $e) {
 294            $this->output .= "Error: $e\n";
 295        }
 296        foreach ($warn as $w) {
 297            $this->output .= "Warning: $w\n";
 298        }
 299        $this->output .= sprintf('Validation: %d error(s), %d warning(s)'."\n",
 300                                       sizeof($err), sizeof($warn));
 301        if ($strict && count($err) > 0) {
 302            $this->output .= "Fix these errors and try again.";
 303            return false;
 304        }
 305        return true;
 306    }
 307
 308    function &getPackager()
 309    {
 310        if (!class_exists('PEAR_Packager')) {
 311            require_once 'PEAR/Packager.php';
 312        }
 313        $a = &new PEAR_Packager;
 314        return $a;
 315    }
 316
 317    function &getPackageFile($config, $debug = false, $tmpdir = null)
 318    {
 319        if (!class_exists('PEAR_Common')) {
 320            require_once 'PEAR/Common.php';
 321        }
 322        if (!class_exists('PEAR_PackageFile')) {
 323            require_once 'PEAR/PackageFile.php';
 324        }
 325        $a = &new PEAR_PackageFile($config, $debug, $tmpdir);
 326        $common = new PEAR_Common;
 327        $common->ui = $this->ui;
 328        $a->setLogger($common);
 329        return $a;
 330    }
 331
 332    function doPackage($command, $options, $params)
 333    {
 334        $this->output = '';
 335        $pkginfofile = isset($params[0]) ? $params[0] : 'package.xml';
 336        $pkg2 = isset($params[1]) ? $params[1] : null;
 337        if (!$pkg2 && !isset($params[0]) && file_exists('package2.xml')) {
 338            $pkg2 = 'package2.xml';
 339        }
 340
 341        $packager = &$this->getPackager();
 342        $compress = empty($options['nocompress']) ? true : false;
 343        $result   = $packager->package($pkginfofile, $compress, $pkg2);
 344        if (PEAR::isError($result)) {
 345            return $this->raiseError($result);
 346        }
 347
 348        // Don't want output, only the package file name just created
 349        if (isset($options['showname'])) {
 350            $this->output = $result;
 351        }
 352
 353        if ($this->output) {
 354            $this->ui->outputData($this->output, $command);
 355        }
 356
 357        return true;
 358    }
 359
 360    function doPackageValidate($command, $options, $params)
 361    {
 362        $this->output = '';
 363        if (count($params) < 1) {
 364            $params[0] = 'package.xml';
 365        }
 366
 367        $obj = &$this->getPackageFile($this->config, $this->_debug);
 368        $obj->rawReturn();
 369        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
 370        $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL);
 371        if (PEAR::isError($info)) {
 372            $info = $obj->fromPackageFile($params[0], PEAR_VALIDATE_NORMAL);
 373        } else {
 374            $archive = $info->getArchiveFile();
 375            $tar = &new Archive_Tar($archive);
 376            $tar->extract(dirname($info->getPackageFile()));
 377            $info->setPackageFile(dirname($info->getPackageFile()) . DIRECTORY_SEPARATOR .
 378                $info->getPackage() . '-' . $info->getVersion() . DIRECTORY_SEPARATOR .
 379                basename($info->getPackageFile()));
 380        }
 381
 382        PEAR::staticPopErrorHandling();
 383        if (PEAR::isError($info)) {
 384            return $this->raiseError($info);
 385        }
 386
 387        $valid = false;
 388        if ($info->getPackagexmlVersion() == '2.0') {
 389            if ($valid = $info->validate(PEAR_VALIDATE_NORMAL)) {
 390                $info->flattenFileList();
 391                $valid = $info->validate(PEAR_VALIDATE_PACKAGING);
 392            }
 393        } else {
 394            $valid = $info->validate(PEAR_VALIDATE_PACKAGING);
 395        }
 396
 397        $err = $warn = array();
 398        if ($errors = $info->getValidationWarnings()) {
 399            foreach ($errors as $error) {
 400                if ($error['level'] == 'warning') {
 401                    $warn[] = $error['message'];
 402                } else {
 403                    $err[] = $error['message'];
 404                }
 405            }
 406        }
 407
 408        $this->_displayValidationResults($err, $warn);
 409        $this->ui->outputData($this->output, $command);
 410        return true;
 411    }
 412
 413    function doSvnTag($command, $options, $params)
 414    {
 415        $this->output = '';
 416        $_cmd = $command;
 417        if (count($params) < 1) {
 418            $help = $this->getHelp($command);
 419            return $this->raiseError("$command: missing parameter: $help[0]");
 420        }
 421
 422        $packageFile = realpath($params[0]);
 423        $dir = dirname($packageFile);
 424        $dir = substr($dir, strrpos($dir, '/') + 1);
 425        $obj  = &$this->getPackageFile($this->config, $this->_debug);
 426        $info = $obj->fromAnyFile($packageFile, PEAR_VALIDATE_NORMAL);
 427        if (PEAR::isError($info)) {
 428            return $this->raiseError($info);
 429        }
 430
 431        $err = $warn = array();
 432        if (!$info->validate()) {
 433            foreach ($info->getValidationWarnings() as $error) {
 434                if ($error['level'] == 'warning') {
 435                    $warn[] = $error['message'];
 436                } else {
 437                    $err[] = $error['message'];
 438                }
 439            }
 440        }
 441
 442        if (!$this->_displayValidationResults($err, $warn, true)) {
 443            $this->ui->outputData($this->output, $command);
 444            return $this->raiseError('SVN tag failed');
 445        }
 446
 447        $version    = $info->getVersion();
 448        $package    = $info->getName();
 449        $svntag     = "$package-$version";
 450
 451        if (isset($options['delete'])) {
 452            return $this->_svnRemoveTag($version, $package, $svntag, $packageFile, $options);
 453        }
 454
 455        $path = $this->_svnFindPath($packageFile);
 456
 457        // Check if there are any modified files
 458        $fp = popen('svn st --xml ' . dirname($packageFile), "r");
 459        $out = '';
 460        while ($line = fgets($fp, 1024)) {
 461            $out .= rtrim($line)."\n";
 462        }
 463        pclose($fp);
 464
 465        if (!isset($options['quiet']) && strpos($out, 'item="modified"')) {
 466            $params = array(array(
 467                'name' => 'modified',
 468                'type' => 'yesno',
 469                'default' => 'no',
 470                'prompt' => 'You have files in your SVN checkout (' . $path['from']  . ') that have been modified but not commited, do you still want to tag ' . $version . '?',
 471            ));
 472            $answers = $this->ui->confirmDialog($params);
 473
 474            if (!in_array($answers['modified'], array('y', 'yes', 'on', '1'))) {
 475                return true;
 476            }
 477        }
 478
 479        if (isset($options['slide'])) {
 480            $this->_svnRemoveTag($version, $package, $svntag, $packageFile, $options);
 481        }
 482
 483        // Check if tag already exists
 484        $releaseTag = $path['local']['base'] . 'tags/' . $svntag;
 485        $existsCommand = 'svn ls ' . $path['base'] . 'tags/';
 486
 487        $fp = popen($existsCommand, "r");
 488        $out = '';
 489        while ($line = fgets($fp, 1024)) {
 490            $out .= rtrim($line)."\n";
 491        }
 492        pclose($fp);
 493
 494        if (in_array($svntag . '/', explode("\n", $out))) {
 495            $this->ui->outputData($this->output, $command);
 496            return $this->raiseError('SVN tag ' . $svntag . ' for ' . $package . ' already exists.');
 497        } elseif (file_exists($path['local']['base'] . 'tags') === false) {
 498            return $this->raiseError('Can not locate the tags directory at ' . $path['local']['base'] . 'tags');
 499        } elseif (is_writeable($path['local']['base'] . 'tags') === false) {
 500            return $this->raiseError('Can not write to the tag directory at ' . $path['local']['base'] . 'tags');
 501        } else {
 502            $makeCommand = 'svn mkdir ' . $releaseTag;
 503            $this->output .= "+ $makeCommand\n";
 504            if (empty($options['dry-run'])) {
 505                // We need to create the tag dir.
 506                $fp = popen($makeCommand, "r");
 507                $out = '';
 508                while ($line = fgets($fp, 1024)) {
 509                    $out .= rtrim($line)."\n";
 510                }
 511                pclose($fp);
 512                $this->output .= "$out\n";
 513            }
 514        }
 515
 516        $command = 'svn';
 517        if (isset($options['quiet'])) {
 518            $command .= ' -q';
 519        }
 520
 521        $command .= ' copy --parents ';
 522
 523        $dir   = dirname($packageFile);
 524        $dir   = substr($dir, strrpos($dir, '/') + 1);
 525        $files = array_keys($info->getFilelist());
 526
 527        array_shift($params);
 528        if (count($params)) {
 529            // add in additional files to be tagged (package files and such)
 530            $files = array_merge($files, $params);
 531        }
 532
 533        $commands = array();
 534        foreach ($files as $file) {
 535            if (!file_exists($file)) {
 536                $file = $dir . DIRECTORY_SEPARATOR . $file;
 537            }
 538            $commands[] = $command . ' ' . escapeshellarg($file) . ' ' .
 539                          escapeshellarg($releaseTag . DIRECTORY_SEPARATOR . $file);
 540        }
 541
 542        $this->output .= implode("\n", $commands) . "\n";
 543        if (empty($options['dry-run'])) {
 544            foreach ($commands as $command) {
 545                $fp = popen($command, "r");
 546                while ($line = fgets($fp, 1024)) {
 547                    $this->output .= rtrim($line)."\n";
 548                }
 549                pclose($fp);
 550            }
 551        }
 552
 553        $command = 'svn ci -m "Tagging the ' . $version  . ' release" ' . $releaseTag . "\n";
 554        $this->output .= "+ $command\n";
 555        if (empty($options['dry-run'])) {
 556            $fp = popen($command, "r");
 557            while ($line = fgets($fp, 1024)) {
 558                $this->output .= rtrim($line)."\n";
 559            }
 560            pclose($fp);
 561        }
 562
 563        $this->ui->outputData($this->output, $_cmd);
 564        return true;
 565    }
 566
 567    function _svnFindPath($file)
 568    {
 569        $xml = '';
 570        $command = "svn info --xml $file";
 571        $fp = popen($command, "r");
 572        while ($line = fgets($fp, 1024)) {
 573            $xml .= rtrim($line)."\n";
 574        }
 575        pclose($fp);
 576        $url_tag = strpos($xml, '<url>');
 577        $url = substr($xml, $url_tag + 5, strpos($xml, '</url>', $url_tag + 5) - ($url_tag + 5));
 578
 579        $path = array();
 580        $path['from'] = substr($url, 0, strrpos($url, '/'));
 581        $path['base'] = substr($path['from'], 0, strrpos($path['from'], '/') + 1);
 582
 583        // Figure out the local paths
 584        $pos = strpos($file, '/trunk/');
 585        if ($pos === false) {
 586            $pos = strpos($file, '/branches/');
 587        }
 588        $path['local']['base'] = substr($file, 0, $pos + 1);
 589
 590        return $path;
 591    }
 592
 593    function _svnRemoveTag($version, $package, $tag, $packageFile, $options)
 594    {
 595        $command = 'svn';
 596
 597        if (isset($options['quiet'])) {
 598            $command .= ' -q';
 599        }
 600
 601        $command .= ' remove';
 602        $command .= ' -m "Removing tag for the ' . $version  . ' release."';
 603
 604        $path = $this->_svnFindPath($packageFile);
 605        $command .= ' ' . $path['base'] . 'tags/' . $tag;
 606
 607
 608        if ($this->config->get('verbose') > 1) {
 609            $this->output .= "+ $command\n";
 610        }
 611
 612        $this->output .= "+ $command\n";
 613        if (empty($options['dry-run'])) {
 614            $fp = popen($command, "r");
 615            while ($line = fgets($fp, 1024)) {
 616                $this->output .= rtrim($line)."\n";
 617            }
 618            pclose($fp);
 619        }
 620
 621        $this->ui->outputData($this->output, $command);
 622        return true;
 623    }
 624
 625    function doCvsTag($command, $options, $params)
 626    {
 627        $this->output = '';
 628        $_cmd = $command;
 629        if (count($params) < 1) {
 630            $help = $this->getHelp($command);
 631            return $this->raiseError("$command: missing parameter: $help[0]");
 632        }
 633
 634        $packageFile = realpath($params[0]);
 635        $obj  = &$this->getPackageFile($this->config, $this->_debug);
 636        $info = $obj->fromAnyFile($packageFile, PEAR_VALIDATE_NORMAL);
 637        if (PEAR::isError($info)) {
 638            return $this->raiseError($info);
 639        }
 640
 641        $err = $warn = array();
 642        if (!$info->validate()) {
 643            foreach ($info->getValidationWarnings() as $error) {
 644                if ($error['level'] == 'warning') {
 645                    $warn[] = $error['message'];
 646                } else {
 647                    $err[] = $error['message'];
 648                }
 649            }
 650        }
 651
 652        if (!$this->_displayValidationResults($err, $warn, true)) {
 653            $this->ui->outputData($this->output, $command);
 654            return $this->raiseError('CVS tag failed');
 655        }
 656
 657        $version    = $info->getVersion();
 658        $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $version);
 659        $cvstag     = "RELEASE_$cvsversion";
 660        $files      = array_keys($info->getFilelist());
 661        $command = 'cvs';
 662        if (isset($options['quiet'])) {
 663            $command .= ' -q';
 664        }
 665
 666        if (isset($options['reallyquiet'])) {
 667            $command .= ' -Q';
 668        }
 669
 670        $command .= ' tag';
 671        if (isset($options['slide'])) {
 672            $command .= ' -F';
 673        }
 674
 675        if (isset($options['delete'])) {
 676            $command .= ' -d';
 677        }
 678
 679        $command .= ' ' . $cvstag . ' ' . escapeshellarg($params[0]);
 680        array_shift($params);
 681        if (count($params)) {
 682            // add in additional files to be tagged
 683            $files = array_merge($files, $params);
 684        }
 685
 686        $dir = dirname($packageFile);
 687        $dir = substr($dir, strrpos($dir, '/') + 1);
 688        foreach ($files as $file) {
 689            if (!file_exists($file)) {
 690                $file = $dir . DIRECTORY_SEPARATOR . $file;
 691            }
 692            $command .= ' ' . escapeshellarg($file);
 693        }
 694
 695        if ($this->config->get('verbose') > 1) {
 696            $this->output .= "+ $command\n";
 697        }
 698
 699        $this->output .= "+ $command\n";
 700        if (empty($options['dry-run'])) {
 701            $fp = popen($command, "r");
 702            while ($line = fgets($fp, 1024)) {
 703                $this->output .= rtrim($line)."\n";
 704            }
 705            pclose($fp);
 706        }
 707
 708        $this->ui->outputData($this->output, $_cmd);
 709        return true;
 710    }
 711
 712    function doCvsDiff($command, $options, $params)
 713    {
 714        $this->output = '';
 715        if (sizeof($params) < 1) {
 716            $help = $this->getHelp($command);
 717            return $this->raiseError("$command: missing parameter: $help[0]");
 718        }
 719
 720        $file = realpath($params[0]);
 721        $obj  = &$this->getPackageFile($this->config, $this->_debug);
 722        $info = $obj->fromAnyFile($file, PEAR_VALIDATE_NORMAL);
 723        if (PEAR::isError($info)) {
 724            return $this->raiseError($info);
 725        }
 726
 727        $err = $warn = array();
 728        if (!$info->validate()) {
 729            foreach ($info->getValidationWarnings() as $error) {
 730                if ($error['level'] == 'warning') {
 731                    $warn[] = $error['message'];
 732                } else {
 733                    $err[] = $error['message'];
 734                }
 735            }
 736        }
 737
 738        if (!$this->_displayValidationResults($err, $warn, true)) {
 739            $this->ui->outputData($this->output, $command);
 740            return $this->raiseError('CVS diff failed');
 741        }
 742
 743        $info1 = $info->getFilelist();
 744        $files = $info1;
 745        $cmd = "cvs";
 746        if (isset($options['quiet'])) {
 747            $cmd .= ' -q';
 748            unset($options['quiet']);
 749        }
 750
 751        if (isset($options['reallyquiet'])) {
 752            $cmd .= ' -Q';
 753            unset($options['reallyquiet']);
 754        }
 755
 756        if (isset($options['release'])) {
 757            $cvsversion = preg_replace('/[^a-z0-9]/i', '_', $options['release']);
 758            $cvstag = "RELEASE_$cvsversion";
 759            $options['revision'] = $cvstag;
 760            unset($options['release']);
 761        }
 762
 763        $execute = true;
 764        if (isset($options['dry-run'])) {
 765            $execute = false;
 766            unset($options['dry-run']);
 767        }
 768
 769        $cmd .= ' diff';
 770        // the rest of the options are passed right on to "cvs diff"
 771        foreach ($options as $option => $optarg) {
 772            $arg = $short = false;
 773            if (isset($this->commands[$command]['options'][$option])) {
 774                $arg = $this->commands[$command]['options'][$option]['arg'];
 775                $short = $this->commands[$command]['options'][$option]['shortopt'];
 776            }
 777            $cmd .= $short ? " -$short" : " --$option";
 778            if ($arg && $optarg) {
 779                $cmd .= ($short ? '' : '=') . escapeshellarg($optarg);
 780            }
 781        }
 782
 783        foreach ($files as $file) {
 784            $cmd .= ' ' . escapeshellarg($file['name']);
 785        }
 786
 787        if ($this->config->get('verbose') > 1) {
 788            $this->output .= "+ $cmd\n";
 789        }
 790
 791        if ($execute) {
 792            $fp = popen($cmd, "r");
 793            while ($line = fgets($fp, 1024)) {
 794                $this->output .= rtrim($line)."\n";
 795            }
 796            pclose($fp);
 797        }
 798
 799        $this->ui->outputData($this->output, $command);
 800        return true;
 801    }
 802
 803    function doPackageDependencies($command, $options, $params)
 804    {
 805        // $params[0] -> the PEAR package to list its information
 806        if (count($params) !== 1) {
 807            return $this->raiseError("bad parameter(s), try \"help $command\"");
 808        }
 809
 810        $obj = &$this->getPackageFile($this->config, $this->_debug);
 811        if (is_file($params[0]) || strpos($params[0], '.xml') > 0) {
 812           $info = $obj->fromAnyFile($params[0], PEAR_VALIDATE_NORMAL);
 813        } else {
 814            $reg  = $this->config->getRegistry();
 815            $info = $obj->fromArray($reg->packageInfo($params[0]));
 816        }
 817
 818        if (PEAR::isError($info)) {
 819            return $this->raiseError($info);
 820        }
 821
 822        $deps = $info->getDeps();
 823        if (is_array($deps)) {
 824            if ($info->getPackagexmlVersion() == '1.0') {
 825                $data = array(
 826                    'caption' => 'Dependencies for pear/' . $info->getPackage(),
 827                    'border' => true,
 828                    'headline' => array("Required?", "Type", "Name", "Relation", "Version"),
 829                    );
 830
 831                foreach ($deps as $d) {
 832                    if (isset($d['optional'])) {
 833                        if ($d['optional'] == 'yes') {
 834                            $req = 'No';
 835                        } else {
 836                            $req = 'Yes';
 837                        }
 838                    } else {
 839                        $req = 'Yes';
 840                    }
 841
 842                    if (isset($this->_deps_rel_trans[$d['rel']])) {
 843                        $rel = $this->_deps_rel_trans[$d['rel']];
 844                    } else {
 845                        $rel = $d['rel'];
 846                    }
 847
 848                    if (isset($this->_deps_type_trans[$d['type']])) {
 849                        $type = ucfirst($this->_deps_type_trans[$d['type']]);
 850                    } else {
 851                        $type = $d['type'];
 852                    }
 853
 854                    if (isset($d['name'])) {
 855                        $name = $d['name'];
 856                    } else {
 857                        $name = '';
 858                    }
 859
 860                    if (isset($d['version'])) {
 861                        $version = $d['version'];
 862                    } else {
 863                        $version = '';
 864                    }
 865
 866                    $data['data'][] = array($req, $type, $name, $rel, $version);
 867                }
 868            } else { // package.xml 2.0 dependencies display
 869                require_once 'PEAR/Dependency2.php';
 870                $deps = $info->getDependencies();
 871                $reg = &$this->config->getRegistry();
 872                if (is_array($deps)) {
 873                    $d = new PEAR_Dependency2($this->config, array(), '');
 874                    $data = array(
 875                        'caption' => 'Dependencies for ' . $info->getPackage(),
 876                        'border' => true,
 877                        'headline' => array("Required?", "Type", "Name", 'Versioning', 'Group'),
 878                        );
 879                    foreach ($deps as $type => $subd) {
 880                        $req = ($type == 'required') ? 'Yes' : 'No';
 881                        if ($type == 'group') {
 882                            $group = $subd['attribs']['name'];
 883                        } else {
 884                            $group = '';
 885                        }
 886
 887                        if (!isset($subd[0])) {
 888                            $subd = array($subd);
 889                        }
 890
 891                        foreach ($subd as $groupa) {
 892                            foreach ($groupa as $deptype => $depinfo) {
 893                                if ($deptype == 'attribs') {
 894                                    continue;
 895                                }
 896
 897                                if ($deptype == 'pearinstaller') {
 898                                    $deptype = 'pear Installer';
 899                                }
 900
 901                                if (!isset($depinfo[0])) {
 902                                    $depinfo = array($depinfo);
 903                                }
 904
 905                                foreach ($depinfo as $inf) {
 906                                    $name = '';
 907                                    if (isset($inf['channel'])) {
 908                                        $alias = $reg->channelAlias($inf['channel']);
 909                                        if (!$alias) {
 910                                            $alias = '(channel?) ' .$inf['channel'];
 911                                        }
 912                                        $name = $alias . '/';
 913
 914                                    }
 915                                    if (isset($inf['name'])) {
 916                                        $name .= $inf['name'];
 917                                    } elseif (isset($inf['pattern'])) {
 918                                        $name .= $inf['pattern'];
 919                                    } else {
 920                                        $name .= '';
 921                                    }
 922
 923                                    if (isset($inf['uri'])) {
 924                                        $name .= ' [' . $inf['uri'] .  ']';
 925                                    }
 926
 927                                    if (isset($inf['conflicts'])) {
 928                                        $ver = 'conflicts';
 929                                    } else {
 930                                        $ver = $d->_getExtraString($inf);
 931                                    }
 932
 933                                    $data['data'][] = array($req, ucfirst($deptype), $name,
 934                                        $ver, $group);
 935                                }
 936                            }
 937                        }
 938                    }
 939                }
 940            }
 941
 942            $this->ui->outputData($data, $command);
 943            return true;
 944        }
 945
 946        // Fallback
 947        $this->ui->outputData("This package does not have any dependencies.", $command);
 948    }
 949
 950    function doSign($command, $options, $params)
 951    {
 952        // should move most of this code into PEAR_Packager
 953        // so it'll be easy to implement "pear package --sign"
 954        if (count($params) !== 1) {
 955            return $this->raiseError("bad parameter(s), try \"help $command\"");
 956        }
 957
 958        require_once 'System.php';
 959        require_once 'Archive/Tar.php';
 960
 961        if (!file_exists($params[0])) {
 962            return $this->raiseError("file does not exist: $params[0]");
 963        }
 964
 965        $obj = $this->getPackageFile($this->config, $this->_debug);
 966        $info = $obj->fromTgzFile($params[0], PEAR_VALIDATE_NORMAL);
 967        if (PEAR::isError($info)) {
 968            return $this->raiseError($info);
 969        }
 970
 971        $tar = new Archive_Tar($params[0]);
 972        $tmpdir = System::mktemp('-d pearsign');
 973        if (!$tar->extractList('package2.xml package.xml package.sig', $tmpdir)) {
 974            return $this->raiseError("failed to extract tar file");
 975        }
 976
 977        if (file_exists("$tmpdir/package.sig")) {
 978            return $this->raiseError("package already signed");
 979        }
 980
 981        $packagexml = 'package.xml';
 982        if (file_exists("$tmpdir/package2.xml")) {
 983            $packagexml = 'package2.xml';
 984        }
 985
 986        if (file_exists("$tmpdir/package.sig")) {
 987            unlink("$tmpdir/package.sig");
 988        }
 989
 990        if (!file_exists("$tmpdir/$packagexml")) {
 991            return $this->raiseError("Extracted file $tmpdir/$packagexml not found.");
 992        }
 993
 994        $input = $this->ui->userDialog($command,
 995                                       array('GnuPG Passphrase'),
 996                                       array('password'));
 997        if (!isset($input[0])) {
 998            //use empty passphrase
 999            $input[0] = '';
1000        }
1001
1002        $devnull = (isset($options['verbose'])) ? '' : ' 2>/dev/null';
1003        $gpg = popen("gpg --batch --passphrase-fd 0 --armor --detach-sign --output $tmpdir/package.sig $tmpdir/$packagexml" . $devnull, "w");
1004        if (!$gpg) {
1005            return $this->raiseError("gpg command failed");
1006        }
1007
1008        fwrite($gpg, "$input[0]\n");
1009        if (pclose($gpg) || !file_exists("$tmpdir/package.sig")) {
1010            return $this->raiseError("gpg sign failed");
1011        }
1012
1013        if (!$tar->addModify("$tmpdir/package.sig", '', $tmpdir)) {
1014            return $this->raiseError('failed adding signature to file');
1015        }
1016
1017        $this->ui->outputData("Package signed.", $command);
1018        return true;
1019    }
1020
1021    /**
1022     * For unit testing purposes
1023     */
1024    function &getInstaller(&$ui)
1025    {
1026        if (!class_exists('PEAR_Installer')) {
1027            require_once 'PEAR/Installer.php';
1028        }
1029        $a = &new PEAR_Installer($ui);
1030        return $a;
1031    }
1032
1033    /**
1034     * For unit testing purposes
1035     */
1036    function &getCommandPackaging(&$ui, &$config)
1037    {
1038        if (!class_exists('PEAR_Command_Packaging')) {
1039            if ($fp = @fopen('PEAR/Command/Packaging.php', 'r', true)) {
1040                fclose($fp);
1041                include_once 'PEAR/Command/Packaging.php';
1042            }
1043        }
1044
1045        if (class_exists('PEAR_Command_Packaging')) {
1046            $a = &new PEAR_Command_Packaging($ui, $config);
1047        } else {
1048            $a = null;
1049        }
1050
1051        return $a;
1052    }
1053
1054    function doMakeRPM($command, $options, $params)
1055    {
1056
1057        // Check to see if PEAR_Command_Packaging is installed, and
1058        // transparently switch to use the "make-rpm-spec" command from it
1059        // instead, if it does. Otherwise, continue to use the old version
1060        // of "makerpm" supplied with this package (PEAR).
1061        $packaging_cmd = $this->getCommandPackaging($this->ui, $this->config);
1062        if ($packaging_cmd !== null) {
1063            $this->ui->outputData('PEAR_Command_Packaging is installed; using '.
1064                'newer "make-rpm-spec" command instead');
1065            return $packaging_cmd->run('make-rpm-spec', $options, $params);
1066        }
1067
1068        $this->ui->outputData('WARNING: "pear makerpm" is no longer available; an '.
1069          'improved version is available via "pear make-rpm-spec", which '.
1070          'is available by installing PEAR_Command_Packaging');
1071        return true;
1072    }
1073
1074    function doConvert($command, $options, $params)
1075    {
1076        $packagexml    = isset($params[0]) ? $params[0] : 'package.xml';
1077        $newpackagexml = isset($params[1]) ? $params[1] : dirname($packagexml) .
1078            DIRECTORY_SEPARATOR . 'package2.xml';
1079        $pkg = &$this->getPackageFile($this->config, $this->_debug);
1080        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
1081        $pf = $pkg->fromPackageFile($packagexml, PEAR_VALIDATE_NORMAL);
1082        PEAR::staticPopErrorHandling();
1083        if (PEAR::isError($pf)) {
1084            if (is_array($pf->getUserInfo())) {
1085                foreach ($pf->getUserInfo() as $warning) {
1086                    $this->ui->outputData($warning['message']);
1087                }
1088            }
1089            return $this->raiseError($pf);
1090        }
1091
1092        if (is_a($pf, 'PEAR_PackageFile_v2')) {
1093            $this->ui->outputData($packagexml . ' is already a package.xml version 2.0');
1094            return true;
1095        }
1096
1097        $gen   = &$pf->getDefaultGenerator();
1098        $newpf = &$gen->toV2();
1099        $newpf->setPackagefile($newpackagexml);
1100        $gen = &$newpf->getDefaultGenerator();
1101        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
1102        $state = (isset($options['flat']) ? PEAR_VALIDATE_PACKAGING : PEAR_VALIDATE_NORMAL);
1103        $saved = $gen->toPackageFile(dirname($newpackagexml), $state, basename($newpackagexml));
1104        PEAR::staticPopErrorHandling();
1105        if (PEAR::isError($saved)) {
1106            if (is_array($saved->getUserInfo())) {
1107                foreach ($saved->getUserInfo() as $warning) {
1108                    $this->ui->outputData($warning['message']);
1109                }
1110            }
1111
1112            $this->ui->outputData($saved->getMessage());
1113            return true;
1114        }
1115
1116        $this->ui->outputData('Wrote new version 2.0 package.xml to "' . $saved . '"');
1117        return true;
1118    }
1119}