cvs: SVNROOT / run-conversion.php

[email protected] ("Gwynne Raskind") Sat, 20 Jun 2009 09:50:55 -0000
Newsgroups svn.migration
Message-ID <cvsgwynne1245491455@cvsserver>
gwynne		Sat Jun 20 09:50:55 2009 UTC

  Modified files:              
    /SVNROOT	run-conversion.php 
  Log:
  - Heavily commented everything
  - run_command() can now return exit status to caller if desired
  - cp() uses DIR_SEP instead of hardcoded /
  - Added -d/--noclean option to leave temporary files in place
  - Rewrite symlink->svn:externals code to hopefully work correctly
gwynne-20090620095055.txt (text/plain, 19.1 KB)
http://cvs.php.net/viewvc.cgi/SVNROOT/run-conversion.php?r1=1.60&r2=1.61&diff_format=u
Index: SVNROOT/run-conversion.php
diff -u SVNROOT/run-conversion.php:1.60 SVNROOT/run-conversion.php:1.61
--- SVNROOT/run-conversion.php:1.60	Wed Dec 17 17:44:19 2008
+++ SVNROOT/run-conversion.php	Sat Jun 20 09:50:55 2009
@@ -7,7 +7,7 @@
 
 // -----------------------------------------------------------------------------------------------------------------------------
 // Constants
-$version = substr('$Revision: 1.60 $', strlen('$Revision: '), -2);
+$version = substr('$Revision: 1.61 $', strlen('$Revision: '), -2);
 
 $passes = array(
     'processcvs',       // Process CVS modules
@@ -26,6 +26,8 @@
 
 // -----------------------------------------------------------------------------------------------------------------------------
 // Utility functions
+
+// Print a verbose message if the current verbosity level allows
 function v($level, $message)
 {
     if ($GLOBALS['options']['verbose'] >= $level) {
@@ -33,12 +35,14 @@
     }
 }
 
+// Note a fatal error and exit. Just die(), really.
 function error($message, $status = 1)
 {
     print $message;
     exit($status);
 }
 
+// Returns true if either 1) We're running all passes, or 2) running the specific pass given
 function is_running_pass($pass)
 {
     return $GLOBALS['options']['pass'] === NULL || $GLOBALS['options']['pass'] == $pass;
@@ -49,6 +53,7 @@
     return !($value === '.' || $value === '..');
 }
 
+// A version of scandir() which doesn't return the . and .. entries
 function scandir_no_meta($directory)
 {
     if (func_num_args() == 3) {
@@ -61,30 +66,38 @@
     return array_filter($results, 'scandir_is_meta');
 }
 
-function run_command($command, $ignoreErrors = FALSE)
+// Run a shell command with verbose commentary. Does NOT escape its argument. Can be made to ignore errors.
+function run_command($command, $ignoreErrors = FALSE, &$exitStatus = NULL)
 {
     v(2, "Running '{$command}'...");
-    exec($command, $output, $exitstatus);
-    if ($exitstatus != 0 && !$ignoreErrors) {
+    exec($command, $output, $lExitStatus);
+    if ($lExitStatus != 0 && !$ignoreErrors) {
         error("\nAn error occurred. Exit status was {$exitstatus}. Output:\n" . implode("\n", $output) . "\n");
-    } else if ($exitstatus != 0) {
+    } else if ($lExitStatus != 0) {
         v(2, " warning: an error occurred.\n");
     } else {
         v(2, " done.\n");
     }
+    if ($exitStatus !== NULL) {
+        $exitStatus = $lExitStatus;
+    }
     return $output;
 }
 
+// Recursive premissions-preserving verbose copy which appends to temp_path/cp-output
 function cp($source, $dest, $ignoreErrors = FALSE)
 {
-    run_command("cp -Rpv " . escapeshellarg($source) . ' ' . escapeshellarg($dest) . ' >> ' . escapeshellarg("{$GLOBALS['temp_path']}/cp-output"), $ignoreErrors);
+    run_command('cp -Rpv ' . escapeshellarg($source) . ' ' . escapeshellarg($dest) .
+                    ' >> ' . escapeshellarg($GLOBALS['temp_path'] . DIR_SEP . 'cp-output'), $ignoreErrors);
 }
 
+// Rename from source to dest.
 function mv($source, $dest)
 {
     run_command("mv " . escapeshellarg($source) . ' ' . escapeshellarg($dest));
 }
 
+// Recursive forced unlink
 function rm($path)
 {
     run_command("rm -Rf " . escapeshellarg($path));
@@ -133,6 +146,12 @@
     'default' => './phpsvn.options.skel',
     'description' => 'Skeleton file to use for cvs2svn conversions.',
     'action' => 'StoreString'));
+$cmdline_parser->addOption('noclean', array(
+    'short_name' => '-d',
+    'long_name' => '--noclean',
+    'description' => 'Don\'t remove temporary files when conversion is done.',
+    'default' => FALSE,
+    'action' => 'StoreTrue'));
 
 try {
     $result = $cmdline_parser->parse();
@@ -173,7 +192,8 @@
     private     $optionsFileContents = NULL;
     private     $outputPath = NULL;
     private     $seed = 0;
-    
+
+    // Read options file, replacing temporary directory reference immediately (not variant per run). 
     public function __construct($seed = NULL)
     {
         $this->optionsFileContents = file_get_contents($GLOBALS['options']['skeleton']);
@@ -181,23 +201,32 @@
         $this->seed = ($seed === NULL ? mt_rand() : $seed);
     }
     
+    // Update the SVN repository path
     public function setOutputPath($path)
     {
         $this->outputPath = $path;
     }
     
+    // Add a CVS module to the options. Special-cases several problematic modules.
     public function addCVSModule($moduleName, $isMetaModule = false)
     {
+        // Path to the module in the original CVS repository
         $cvspath = "{$GLOBALS['temp_path']}/cvsrepo/{$moduleName}/";
+        // A meta-module is one which contains a list of other modules that need to be individually handled.
+        // Currently this includes only PEAR's modules and PECL's extensions
         if ($isMetaModule) {
+            // Prefix of module
             $modprefix = $moduleName . '/';
+            // For each submodule in the meta-module...
             foreach(scandir_no_meta($cvspath) as $module) {
+                // Special case for problematic modules
                 if (!is_dir($cvspath . $module) || 
                         count(scandir($cvspath . $module)) == 2 || $module == 'Attic' ||
                         ($moduleName == 'php/pecl' && $module == 'libextractor') ||
                         ($moduleName == 'pear/modules' && in_array($module, array('HTML_QuickForm_ComboBox', 'Services_Compete', 'XML_HTMLSax3')))) {
                     continue; // empty dir
                 }
+                // Add the submodule as a subdirectory of the meta-module. Notice the meta-module itself is never added.
                 $this->optionsFileContents .= <<<EOEXTRA
 run_options.add_project(
     '{$cvspath}{$module}',
@@ -211,6 +240,7 @@
 EOEXTRA;
             }
         } else {
+            // Add the plain old module as a root directory
             $this->optionsFileContents .= <<<EOEXTRA
 run_options.add_project(
     '{$cvspath}',
@@ -227,10 +257,13 @@
     
     public function run()
     {
+        // Replace output repository path here now that it can't change again.
         $this->optionsFileContents = str_replace('@@@OUTPUT_PATH@@@', $this->outputPath, $this->optionsFileContents);
+        // Choose a random filename for the options file, which has to exist on disk (grumble)
         $filename = $GLOBALS['temp_path'] . DIR_SEP . 'cvs2svn.options.' . $this->seed;
         file_put_contents($filename, $this->optionsFileContents);
 
+        // Run the cvs2svn command with various pipes.
         $command = "exec cvs2svn --options=" . escapeshellarg($filename);
         v(2, "Running: '{$command}'...");
         $cvs2svn_process = proc_open($command, array(
@@ -240,6 +273,7 @@
             ), $pipes, NULL, NULL);
         fclose($pipes[0]);
         $procinfo = array();
+        // Sleep and let cvs2svn do its work. 500,000us is 1/2 second. Could probably push that even higher without noticable penalty.
         do {
             usleep(500000);
             $procinfo = proc_get_status($cvs2svn_process);
@@ -285,9 +319,17 @@
 
     $converter = new CVS2SVNConverter;
     
+    // Copy the real CVSROOT to a temporary staging area, since we'll be modding it heavily.
     $cvsPath = "{$GLOBALS['temp_path']}/cvsrepo";
     cp($GLOBALS['options']['cvsroot'], $cvsPath, TRUE);
     
+    // Some renames:
+    //      cvs/phpdoc      => cvs/doc-base ; Will be moved into phpdoc/ later
+    //      cvs/phpdoc/en   => cvs/phpdoc-en ; Will be renamed to "en" and moved into phpdoc/ later
+    //      cvs/pear        => cvs/modules ; This will be moved into a "pear" directory later
+    //      cvs/TSRM        => cvs/php-src/TSRM ; Merge TSRM into php-src
+    //      cvs/ZendEngine2 => cvs/php-src/ZendEngine2 ; Merge ZE2 into php-src
+    //      cvs/pdo-specs   => cvs/pecl/pdo/specs ; This didn't belong as a separate module to begin with and will be merged into PECL's PDO
     mv("{$cvsPath}/phpdoc", "{$cvsPath}/doc-base");
     mv("{$cvsPath}/doc-base/en", "{$cvsPath}/phpdoc-en");
     mv("{$cvsPath}/pear", "{$cvsPath}/modules");
@@ -295,14 +337,17 @@
     mv("{$cvsPath}/ZendEngine2", "{$cvsPath}/php-src/ZendEngine2");
     mv("{$cvsPath}/pdo-specs", "{$cvsPath}/pecl/pdo/specs");
     
+    // There are several symlinks in php-src to PECL extensions which confuse cvs2svn, so remove them. These will be properly restored later.
     $links = array('bz2', 'hash', 'oci8', 'pdo', 'pdo_dblib', 'pdo_firebird', 'spl', 'soap', 'sqlite', 'tidy', 'xmlreader', 'xmlwriter', 'filter', 'ircg',
         'json', 'pdo_mysql', 'pdo_oci', 'pdo_odbc', 'pdo_pgsql', 'pdo_sqlite', 'simplexml', 'fileinfo');
     foreach ($links as $link) {
         rm("{$cvsPath}/php-src/ext/{$link}");
     }
-
+    
+    // Get a base list of CVS modules.
     $cvs_modules = scandir_no_meta($cvsPath);
-
+    
+    // Setup the basic directory structure in the CVS root for restructure purposes
     mkdir("{$cvsPath}/gtk");
     mkdir("{$cvsPath}/php");
     mkdir("{$cvsPath}/pear");
@@ -310,23 +355,32 @@
     mkdir("{$cvsPath}/phpdoc");
     
     foreach ($cvs_modules as $cvs_module) {
+        // CVSROOT is not converted. Period.
         if ($cvs_module == 'CVSROOT') {
             continue;
         }
+        // Raw files in the cvs repo, empty directories (count(scandir()) == 2 (./..)), and selected obsolete modules are not converted.
         if (!is_dir("{$cvsPath}/{$cvs_module}") || count(scandir("{$cvsPath}/{$cvs_module}")) == 2 ||
                 in_array($cvs_module, array('smarty', 'smarty-web', 'php4.fubar', 'php4.unused', 'peardoc.backup', 'php3', 'phpfi', 'livedocs',
                     'Zend', 'functable', 'dialin', 'docstuff', 'jpgraph', 'phpoc_de', 'phpdoc-fa_IR', 'lxr', 'portal', 'pres', 'embed', 'imapd'))) {
             rm("{$cvsPath}/{$cvs_module}");
+        // Modules belonging to PHP-GTK go into cvs/gtk/<module>
         } else if (in_array($cvs_module, array('php-gtk', 'php-gtk-doc', 'old-php-gtk-modules', 'php-gtk-web'))) {
             mv("{$cvsPath}/{$cvs_module}", "{$cvsPath}/gtk/{$cvs_module}");
             $converter->addCVSModule("gtk/{$cvs_module}");
+        // Modules belonging to the PHP base go into cvs/php/<module>. Note that this includes PECL!
+        //  PECL is a meta-module. (see CVS2SVNConverter::addCVSModule)
         } else if (in_array($cvs_module, array('ZendAPI', 'php-src', 'pecl', 'php-objc', 'php-lang',
                 'win-installer', 'bindlib_w32', 'zlib', 'pdo-specs', 'php-internals-win', 'fastcgi-isapi'))) {
             mv("{$cvsPath}/{$cvs_module}", "{$cvsPath}/php/{$cvs_module}");
             $converter->addCVSModule("php/{$cvs_module}", $cvs_module == 'pecl');
+        // Modules belonging to PEAR (especially the "modules" directory which contains all PEAR modules) go into cvs/pear/<module>
+        //  PEAR/modules is a meta-module. (see CVS2SVNConverter::addCVSModule)
         } else if (in_array($cvs_module, array('modules', 'pearbot', 'pear-core', 'peardoc', 'pearweb'))) {
             mv("{$cvsPath}/{$cvs_module}", "{$cvsPath}/pear/{$cvs_module}");
             $converter->addCVSModule("pear/{$cvs_module}", $cvs_module == 'modules');
+        // Modules belonging to websites, including the presentation, bugtracker, and pecl4win modules, go into cvs/web/<module>
+        //  All modules that end in web are matched, and added with the "-?web" suffix stripped.
         } else if (substr($cvs_module, -3) == "web" || in_array($cvs_module, array('pres2', 'presentations', 'bugtracker', 'pecl4win'))) {
             if (substr($cvs_module, -4) == '-web') {
                 $dest_cvs_module = substr($cvs_module, 0, -4);
@@ -337,6 +391,8 @@
             }
             mv("{$cvsPath}/{$cvs_module}", "{$cvsPath}/web/{$dest_cvs_module}");
             $converter->addCVSModule("web/{$dest_cvs_module}");
+        // Modules belonging to phpdoc (especially doc-base, see renamings) go into cvs/phpdoc/<module>
+        //  All modules matching "phpdoc-(.*)" are matched, and added with the "phpdoc-" prefix stripped.
         } else if (strncmp($cvs_module, "phpdoc", 6) == 0 || $cvs_module == 'doc-base') {
             if (substr($cvs_module, 0, 7) == 'phpdoc-') {
                 $dest_cvs_module = substr($cvs_module, 7);
@@ -345,11 +401,13 @@
             }
             mv("{$cvsPath}/{$cvs_module}", "{$cvsPath}/phpdoc/{$dest_cvs_module}");
             $converter->addCVSModule("phpdoc/{$dest_cvs_module}");
+        // All other modules go into cvs/<module>. There shouldn't be any of these.
         } else {
             $converter->addCVSModule($cvs_module);
         }
     }
     
+    // Strip any .svn dirs that might've been accidentally committed to CVS from other checkouts to avoid confusing the heck out of cvs2svn
     run_command('find ' . escapeshellarg("{$cvsPath}/pear/modules") . " -name '.svn' -type d -print0 | xargs -0 rm -Rf");
 }
     
@@ -397,6 +455,8 @@
 // ignores pass
 function pass_ignores()
 {
+    // Recursively processes the entire converted SVN repository, converting .cvsignore files to svn:ignore properties.
+    // XXX THIS MAY NO LONGER BE NECESSARY AS OF cvs2svn 2.2.0!
     $svnPath = "{$GLOBALS['options']['svnroot']}/repository";
     $coDir = "{$GLOBALS['temp_path']}/ignore_co";
     $dirStack = array($svnPath);
@@ -425,13 +485,17 @@
 // link pass
 function pass_link()
 {
+    // Re-link the PECL extensions to php-src using svn:externals.
+    // XXX THERE IS A BUG HERE WHICH IS CAUSING EXTENSIONS TO BE LINKED INTO BRANCHES THEY SHOULDN'T BE
     $svnPath = "{$GLOBALS['options']['svnroot']}/repository/php/php-src";
-
+    
+    // Get a complete list of tags and branches in the converted SVN repository
     $rawTagList = run_command('exec svn ls file:///' . escapeshellarg("{$svnPath}/tags"));
     $rawBranchList = run_command('exec svn ls file:///' . escapeshellarg("{$svnPath}/branches"));
     
     $oldcwd = getcwd();
 
+    // Creates a sparse checkout of the top levels ONLY of php/php-src/trunk, php/php-src/branches/*, and php/php-src/tags/*
     $sparseCoPath = "{$GLOBALS['temp_path']}/php-src-sparse-co";
     $tmpfilePath = "{$GLOBALS['temp_path']}/sparse-co-output";
     if (!file_exists($sparseCoPath)) {
@@ -456,52 +520,47 @@
     
     chdir($sparseCoPath);
     
-    $tagList = array();
-    $branchList = array();
-    foreach ($rawTagList as $tag) {
-        $realTag = substr($tag, 0, -1);
-        $xml = run_command('exec svn info --xml file:///' . escapeshellarg("{$svnPath}/tags/{$realTag}"));
-        $xml = implode("\n", $xml);
-        $tagList[$realTag] = strtotime(substr($xml, strpos($xml, '<date>') + 6, strlen('0000-00-00T00:00:00.000000Z')));
-        run_command('exec svn propdel svn:externals ' . escapeshellarg('tags' . DIR_SEP . $realTag . DIR_SEP . 'ext') . ' 2>&1');
-    }
-    foreach ($rawBranchList as $branch) {
-        $realBranch = substr($branch, 0, -1);
-        $xml = run_command('exec svn info --xml file:///' . escapeshellarg("{$svnPath}/branches/{$realBranch}"));
-        $xml = implode("\n", $xml);
-        $branchList[$realBranch] = strtotime(substr($xml, strpos($xml, '<date>') + 6, strlen('0000-00-00T00:00:00.000000Z')));
-        run_command('exec svn propdel svn:externals ' . escapeshellarg('branches' . DIR_SEP . $realBranch . DIR_SEP . 'ext') . ' 2>&1');
-    }
-    run_command('exec svn propdel svn:externals ' . escapeshellarg('trunk' . DIR_SEP . 'ext') . ' 2>&1');
-    
+    // List of extensions that should be linked from PECL.
+    //  Note: This list is accurate as of 2009-06-20
     $links = array('bz2', 'hash', 'oci8', 'pdo', 'pdo_dblib', 'pdo_firebird', 'spl', 'soap', 'sqlite', 'tidy', 'xmlreader', 'xmlwriter', 'filter', 'json',
         'pdo_mysql', 'pdo_oci', 'pdo_odbc', 'pdo_pgsql', 'pdo_sqlite', 'simplexml', 'fileinfo');
     
+    // For each link, get its creation date from the ORIGINAL CVS root.
+    //  Then, for each tag and branch, see whether the given link has a same-named directory. If so, make a link. Always make a link for trunk.
     $props = array();
-    
     foreach ($links as $link) {
-        $info = lstat("{$GLOBALS['options']['cvsroot']}/php-src/ext/{$link}"); // Use the original CVS root here so we get proper timestamps
-        $createDate = $info['ctime'];
-        foreach ($tagList as $tag => $tagDate) {
-            $path = 'tags' . DIR_SEP . $tag . DIR_SEP . 'ext';
-            if ($createDate <= $tagDate) {
-                $props[$path] = (isset($props[$path]) ? $props[$path] : '') . "^/php/pecl/{$link}/tags/{$tag} {$link}\n";
+        foreach ($rawTagList as $tag) {
+            $tag = substr($tag, 0, -1);
+            $externalPath = $svnPath . "/php/pecl/{$link}/tags/{$tag}";
+            run_command("exec svn ls file:///" . escapeshellarg($externalPath) . " 2>/dev/null", TRUE, $status);
+            if ($status === 0) {
+                v(2, "Tag {$tag} exists in ^/php/pecl/{$link}/tags. Adding external.\n");
+                $path = 'tags' . DIR_SEP . $tag . DIR_SEP . 'ext';
+                $props[$path] = (isset($props[$path]) ? $props[$path] : '') . "^/php/pecl/{$link}/tags/{$tag} {$link}";
+            } else {
+                v(2, "Tag {$tag} does NOT exist in ^/php/pecl/{$link}/tags. NOT adding external.\n");
             }
         }
-        foreach ($branchList as $branch => $branchDate) {
-            $path = 'branches' . DIR_SEP . $branch . DIR_SEP . 'ext';
-            if ($createDate <= $branchDate) {
-                $props[$path] = (isset($props[$path]) ? $props[$path] : '') . "^/php/pecl/{$link}/branches/{$branch} {$link}\n";
+        foreach ($rawBranchList as $branch) {
+            $branch = substr($branch, 0, -1);
+            $externalPath = $svnPath . "/php/pecl/{$link}/branches/{$branch}";
+            run_command("exec svn ls file:///" . escapeshellarg($externalPath) . " 2>/dev/null", TRUE, $status);
+            if ($status === 0) {
+                v(2, "Branch {$branch} exists in ^/php/pecl/{$link}/branches. Adding external.\n");
+                $path = 'branches' . DIR_SEP . $branch . DIR_SEP . 'ext';
+                $props[$path] = (isset($props[$path]) ? $props[$path] : '') . "^/php/pecl/{$link}/branches/{$branch} {$link}";
+            } else {
+                v(2, "Branch {$branch} does NOT exist in ^/php/pecl/{$link}/branches. NOT adding external.\n");
             }
         }
         $path = 'trunk' . DIR_SEP . 'ext';
-        $props[$path] = (isset($props[$path]) ? $props[$path] : '') . "^/php/pecl/{$link}/trunk {$link}\n";
+        $props[$path] = (isset($props[$path]) ? $props[$path] : '') . "^/php/pecl/{$link}/trunk {$link}";
     }
-
+    
     foreach ($props as $path => $value) {
         run_command('exec svn propset svn:externals ' . escapeshellarg($value) . ' ' . escapeshellarg($path));
     }
-    run_command('exec svn commit -m "[SVN CONVERSION] Reorganization in repository php-src."');
+    run_command('exec svn commit -m "[SVN CONVERSION] Reorganization in repository php-src (committing externals in php-src)."');
     
     chdir($oldcwd);
 }
@@ -532,7 +591,7 @@
 // -----------------------------------------------------------------------------------------------------------------------------
 // Cleanup
 
-if ($options['pass'] === NULL) {
+if ($options['pass'] === NULL && $options['noclean'] === FALSE) {
     v(2, "Removing temporary files.\n");
     system("rm -Rf " . escapeshellarg($temp_path));
 }