#!/usr/bin/php Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // Only let this run from the command line! if (php_sapi_name()!=='cli') { exit('Bad sapi name ('.php_sapi_name().')!'); } // Note: Set environment variable DARCS to use a different darcs binary. if(isset($_ENV['DARCS']) && file_exists($_ENV['DARCS'])) { $darcspath = $_ENV['DARCS']; } elseif (file_exists('/usr/local/bin/darcs')) { $darcspath = '/usr/local/bin/darcs'; } else { echo "Error: Can not find darcs!\n"; echo "Set environment variable DARCS to a valid binary.\n"; exit(); } // run $version = '0.3.5a'; $version_prefs_min = '0.3.1'; $version_darcs_min = '2.1.2'; $version_php_min = '5.2.4'; $dname = '_darcs'; $stable_name = 'stable'; $conf_name = '_mdarcs/conf.json'; $command_names = array('help', 'init', 'module', 'whatsnew', 'folder', 'record', 'tag', 'apply', 'pull', 'pullall'); $subcommand_names_module = array('add', 'edit', 'show', 'test'); $subcommand_names_folder = array('record'); $help_topics = array('topics', 'about', 'developers', 'convert'); $repo_base = ''; // set by load_conf $magic_module_names = array('_folder'); $conf = false; dispatch_command(); function json_indent($in, $verify=true, $conf=null) { $token_regexp = '/[[\]{},:]|(? ' ', 'padding' => ' ', 'break' => "\n" ); $level = 0; $out = ''; // find tokens preg_match_all($token_regexp, $in, $tokens, PREG_OFFSET_CAPTURE); $tokens = $tokens[0]; // process tokens while (!empty($tokens)) { // consume current, and get next token $tok = array_shift($tokens); $ntok = empty($tokens)?array('', strlen($in)-1):$tokens[0]; // open if (in_array($tok[0], $token_open)) { $level++; $out .= $tok[0]; $out .= $conf['break']; $out .= repeat_str($level, $conf['indent']); $out .= substr($in, $tok[1]+1, $ntok[1]-($tok[1]+1)); continue; } // quote if (in_array($tok[0], $token_quote)) { // skip quoted tokens array_shift($tokens); // consume ntok while ($ntok[0]!=='"' && !empty($tokens)) { $ntok = array_shift($tokens); } $out .= substr($in, $tok[1], ($ntok[1]+1)-$tok[1]); continue; } // segment break if (in_array($tok[0], $token_segment_break)) { $out .= $tok[0]; $out .= $conf['break']; $out .= repeat_str($level, $conf['indent']); $out .= substr($in, $tok[1]+1, $ntok[1]-($tok[1]+1)); continue; } // segment no break if (in_array($tok[0], $token_segment_nobreak)) { $out .= $tok[0]; $out .= $conf['padding']; $out .= substr($in, $tok[1]+1, $ntok[1]-($tok[1]+1)); continue; } // close if (in_array($tok[0], $token_close)) { $level--; $out .= $conf['break']; $out .= repeat_str($level, $conf['indent']); $out .= $tok[0]; $out .= substr($in, $tok[1]+1, $ntok[1]-($tok[1]+1)); continue; } } // verify json if ($verify===true) { $out_test = json_encode(json_decode($out)); if ($out_test!==$in) { // error in indenting! trigger_error('Indenting JSON failed, returning unindented form!'); return $in; } } return $out; } function repeat_str($count, $str) { $out = ''; for ($i=0; $i<$count; $i++) { $out .= $str; } return $out; } function compare_numeric_version($comp, $ref) { // returns: // 1: $comp> $ref // 0: $comp==$ref // -1: $comp <$ref // -2: not a version number $comp = explode('.', $comp); $comp = preg_replace('/[^0-9]/', '', $comp); $comp_n = count($comp); $ref = explode('.', $ref); $ref = preg_replace('/[^0-9]/', '', $ref); $ref_n = count($ref); $nmax = max($comp_n, $ref_n); if (($comp_n==1 && $comp[0]=='') || ($ref_n==1 && $ref[0]=='')) { return -2; } $ret = 0; $i=0; while ($i<$nmax) { $c = ($comp_n>$i)?intval($comp[$i]):0; $r = ($ref_n>$i)?intval($ref[$i]):0; if ($c>$r) { $ret = 1; break; } elseif ($c<$r) { $ret = -1; break; } else { $i++; } } return $ret; } function find_repo_base($fail_ok=false) { global $dname; // get current dir $pwd = $_SERVER['PWD']; // find project root while (!file_exists($pwd.'/'.$dname) && $pwd!==dirname($pwd)) { $pwd = dirname($pwd); } // got darcs? if (!file_exists($pwd.'/'.$dname)) { if ($fail_ok) { return ''; } else { echo "There does not appear to be a repository here!\n". "Try 'darcs whatsnew' to verify this.\n"; echo "\n==== mdarcs help ====\n"; run_help_general(); exit(); } } return $pwd; } function load_conf($fail_ok=false) { global $repo_base, $dname, $conf_name; // find repo $repo_base = find_repo_base($fail_ok); // look for conf if (!file_exists($repo_base.'/'.$conf_name)) { if ($fail_ok) { return false; } else { echo "No configuration file found at: ".$repo_base.'/'.$conf_name."\n"; echo 'Maybe you should run "mdarcs init" here.'."\n"; echo "\n==== mdarcs help ====\n"; run_help_general(); exit(); } } // load the conf $conf = json_decode(file_get_contents($repo_base.'/'.$conf_name), true); if (!is_array($conf)) { echo "Error parsing config file: ".$repo_base.'/'.$conf_name."\n"; exit(); } return $conf; } function get_author() { global $repo_base, $dname, $conf_name; $author_file = $repo_base.'/'.$dname.'/prefs/author'; $author = false; if (file_exists($author_file)) { // load the author $author = trim(file_get_contents($author_file)); } while ($author===false || strlen($author)<$min_length) { // set author if missing echo <<'. If you provide one now it will be stored in the file '_darcs/prefs/author' and used as a default in the future. To change your preferred author address, simply delete or edit this file. EOS; $author = valid_input("Enter author: ", '#^.+@.+$#'); // write to file if (!file_put_contents($author_file, $author)) { echo "Error writing author to file: {$author_file}\n"; exit(); } } return $author; } function valid_input($question, $test_regexp) { // get input echo $question; $input = trim(fgets(STDIN)); while (preg_match($test_regexp, $input)==false) { // request again for valid input echo $question; $input = trim(fgets(STDIN)); } return $input; } function prepare_path($path, $type=null) { // Adjust that path before matching... // - remove './' from the beginning (optional) // - add '/' to the end of directory names (required) //FIXME!! Convert Windows style paths to Un*x paths if needed to preserve correct expression matching! $path = trim($path); if (substr($path, 0, 2)==='./') { $path = substr($path, 2); } if ($type==='adddir' || $type==='rmdir' || $type==="\033[01;34madddir\033[00m" || $type==="\033[01;34mrmdir\033[00m") { $path = $path.'/'; } // also recognize with output coloring! return $path; } function in_modules($path) { // Report ALL modules that match the given path.... Should ONLY report one module! // Path should already have been prepared with prepare_path(). global $conf; $mods = array(); $mdata = null; foreach ($conf['modules'] as $mod => $mdata) { if (!isset($mdata['filter'])) { continue; } if (preg_match($mdata['filter'], $path)===1) { $mods[] = $mod; } //echo "*"; } //echo "\n"; return $mods; } function preg_match_subset($pattern, $subject_array, $exact_match=false) { // returns the array elements that match // If exact_match is set, don't return ambiguous matches when there is an exact match. $matches = array(); foreach ($subject_array as $key => $value) { if (preg_match($pattern, $value)===1) { $matches[$key] = $value; } } if ($exact_match!==false && in_array($exact_match, $subject_array, true)) { $matches = array(); $matches[] = $exact_match; } return $matches; } function local_mdarcs_modules($verbose=true) { // returns an array of mdarcs modules with files/patches in this repository // Note: Often a subset of the defined mdarcs modules for the repository. // // There may be some false positives for files that are not recorded in darcs. // global $repo_base; $pull_mods = array(); $base = $repo_base.'/'; $bclip = strlen($base); $dstack = array($base); $d = null; $path = null; $mods = null; while(count($dstack)>0) { $d = dir(array_pop($dstack)); while (($path = $d->read())!==false) { if ($path=='.' || $path=='..' || $path=='_darcs') { continue; } $path = substr($d->path.$path, $bclip); if (is_dir($base.$path)) { $path = $path.'/'; array_push($dstack, $base.$path); } $mods = in_modules($path); if (count($mods)>1) { echo "WARNING: File '".$path."' matches multiple modules (".implode(', ', $mods).")."; } else { // only count good module matches $pull_mods[$mods[0]] = $mods[0]; } } $d->close(); } // sort ksort($pull_mods); return $pull_mods; } function dispatch_command() { global $conf, $darcspath, $version, $command_names, $subcommand_names_module, $subcommand_names_folder, $help_topics; /// An ugly way to parse the limited number of arguments and options that mdarcs understands /// // Valid commands (unknown opts passed to darcs, only handles commands that need wrapping): // mdarcs --version // mdarcs topics --help // mdarcs --help // mdarcs help // mdarcs init [--from-repo ] // mdarcs module add [-r] [-v] // mdarcs module edit [-v] // mdarcs module show [] [-a] [-v] // mdarcs module test [-v] // mdarcs whatsnew [] // mdarcs folder record [-a] // mdarcs record [-a] // mdarcs tag [--stable] // mdarcs apply [-v] [--preview] // mdarcs pull [--stable] [--preview] [] // mdarcs pullall [--update] [--stable] [--preview] [] // --update: only pull modules for which at least one file already exists! // --preview: synonym for --dry-run // parse args & opts $cmd = $_SERVER['argv'][0]; $args = array(); $opts = array(); for ($i=1; $i<$_SERVER['argc']; $i++) { if (substr($_SERVER['argv'][$i], 0, 1)==='-') { $opts[] = $_SERVER['argv'][$i]; } else { $args[] = $_SERVER['argv'][$i]; } } if ($cmd=='darcs') { echo "Use mdarcs for handling 'module branches' in darcs.\n". "This is not a repacement for darcs!\n"; exit(); } // version if (in_array('--version', $opts, true)) { run_version(); } // help as command (proxy for help as option) if (count($args)>1 && ($args[0]==='help' || // just hard-code the expansion for help... $args[0]==='hel' || $args[0]==='he' || $args[0]==='h' )) { array_shift($args); // shift 'help' command off the front $opts[] = '--help'; // convert to option } // check for unambiguous command names if (count($args)==0) { run_help_general(); } if (in_array('-h', $opts, true) || in_array('--help', $opts, true)) { // if this is a help request, include help topics in expansion $command = preg_match_subset("#^{$args[0]}#", array_merge($command_names, $help_topics), $args[0]); } else { $command = preg_match_subset("#^{$args[0]}#", $command_names, $args[0]); } if (count($command)>1) { echo 'Ambiguous command name matches: '.implode(', ', $command)."\n\n"; } else { $command = array_pop($command); } // check for subcommands $subcommand = false; if ($command==='module') { // module subcommands $subcommand = preg_match_subset("#^{$args[1]}#", $subcommand_names_module, $args[1]); if (count($subcommand)>1) { echo 'Ambiguous subcommand name matches: '.implode(', ', $subcommand)."\n\n"; } else { $subcommand = array_pop($subcommand); } } if ($command==='folder') { // folder subcommands $subcommand = preg_match_subset("#^{$args[1]}#", $subcommand_names_folder, $args[1]); if (count($subcommand)>1) { echo 'Ambiguous subcommand name matches: '.implode(', ', $subcommand)."\n\n"; } else { $subcommand = array_pop($subcommand); } } // help as option if (in_array('-h', $opts, true) || in_array('--help', $opts, true)) { if (in_array($command, array_merge($command_names, $help_topics), true)) { run_help_topic($command, $subcommand); } else { run_help_general(); } } // load conf or init repo if ($command==='init') { if (count($args)===1 && count($opts)===0) { run_init(); } elseif (count($args)===2 && in_array('--from-repo', $opts, true)) { run_init($args[1]); } else { run_help_general(); } } else { $conf = load_conf(); } // module add if ($command==='module' && $subcommand==='add' && count($args)===4) { $recursive = in_array('-r', $opts, true); $verbose = in_array('-v', $opts, true); $mod = $args[2]; $mod_dir = $args[3]; if (isset($conf['modules'][$mod])) { echo "Module '{$mod}' already defined.\n"; exit(); } run_module_add($mod, $mod_dir, $recursive, $verbose); } // module edit if ($command==='module' && $subcommand==='edit' && count($args)===3) { $verbose = in_array('-v', $opts, true); $mod = $args[2]; if (!isset($conf['modules'][$mod])) { echo "Module '{$mod}' is not defined. Use 'mdarcs module add'.\n"; exit(); } run_module_edit($mod, $verbose); } // module show if ($command==='module' && $subcommand==='show') { $verbose = in_array('-v', $opts, true); $all = in_array('-a', $opts, true); if (count($args)===3) { $mod = $args[2]; if (!isset($conf['modules'][$mod])) { echo "Module '{$mod}' is not defined.\n"; exit(); } } else { $mod = false; } run_module_show($mod, $all, $verbose); } // module test if ($command==='module' && $subcommand==='test') { $verbose = in_array('-v', $opts, true); run_module_test($verbose); } // whatsnew if ($command==='whatsnew') { if (count($args)===2) { $module = $args[1]; } else { $module = false; } if ($module && !isset($conf['modules'][$module])) { echo "Module '{$module}' not defined.\n"; exit(); } run_whatsnew($module); } // folder if ($command==='folder' && $subcommand==='record') { $all = in_array('-a', $opts, true); run_folder_record($all); } // record if (count($args)===2 && $command==='record') { $all = in_array('-a', $opts, true); if (!isset($conf['modules'][$args[1]])) { echo "Module '{$args[1]}' not defined.\n"; exit(); } run_record($args[1], $all); } // tag if (count($args)===2 && $command==='tag') { $stable = in_array('--stable', $opts, true); if (!isset($conf['modules'][$args[1]])) { echo "Module '{$args[1]}' not defined.\n"; exit(); } run_tag($args[1], $stable); } // apply if (count($args)===2 && $command==='apply') { $verbose = in_array('-v', $opts, true); $preview = in_array('--preview', $opts, true) || in_array('--dry-run', $opts, true); $patchfile = $args[1]; if (!is_file($patchfile)) { echo "Can not find patchfile '{$patchfile}'.\n"; exit(); } run_apply($patchfile, $verbose, $preview); } // pull if (count($args)>1 && $command==='pull') { $stable = in_array('--stable', $opts, true); $preview = in_array('--preview', $opts, true) || in_array('--dry-run', $opts, true); if (!isset($conf['modules'][$args[1]])) { echo "Module '{$args[1]}' not defined.\n"; exit(); } if (isset($args[2])) { run_pull($args[1], $stable, $preview, $args[2]); } else { run_pull($args[1], $stable, $preview); } } // pullall if (count($args)>=1 && $command==='pullall') { $update = in_array('--update', $opts, true); $stable = in_array('--stable', $opts, true); $preview = in_array('--preview', $opts, true) || in_array('--dry-run', $opts, true); if (isset($args[1])) { run_pullall($update, $stable, $preview, $args[1]); } else { run_pullall($update, $stable, $preview); } } // fallback run_help_general(); } function run_help_general() { echo << [] [OPTIONS] See: mdarcs help topics Examples: mdarcs --version mdarcs --help mdarcs help mdarcs init [--from-repo ] mdarcs module add [-r] [-v] mdarcs module edit [-v] mdarcs module show [] [-a] [-v] mdarcs module test [-v] mdarcs whatsnew [] mdarcs folder record [-a] mdarcs record [-a] mdarcs tag [--stable] mdarcs apply [-v] [--preview] mdarcs pull [--stable] [--preview] [] mdarcs pullall [--update] [--stable] [--preview] [] NOTE: An mdarcs repository is a darcs repository. However, incorrect use of darcs to manage an mdarcs repository will break the modularity of that repository. It is the duty of all repository users to ensure correct maintenance of a modular repository. EOS; exit(); } function run_help_topic($topic, $subtopic=false) { global $help_topics, $command_names; if ($topic=='topics') { echo "Available topics: ".implode(', ', array_merge($help_topics, $command_names))."\n"; } elseif ($topic=='about') { echo <<] Arguments: see the --from-repo option Options: --from-repo Start with module definitions from the remote repository. Initializes a new mdarcs configuration file, and darcs repository (if needed). This produces some unsaved changes that need to be recorded. Follow this command with "mdarcs record -a mdarcs", or use the module commands to define some more modules first. Use the --from-repo option when you want to pull in a select subset of modules from an existing repository. EOS; } elseif ($topic=='module') { if ($subtopic=='add') { echo << [-r] [-v] Arguments: the mdarcs module name to add the directory in the repository for the base of the module Options: -r Recursive, module contains and all subdirectories. By default, the module only contains and the files in . -v verbose Adds a simple mdarcs module definition to the project. Modules may be recursive or contain only the specified directory, but they may not overlap. The coverage of new modules is automatically tested after their creation. For advanced module definitions, use this command to create the new module and then edit the regular expression using "mdarcs module edit". Module names may not start with '_'. This prefix is reserved for magic modules with special semantics. Note that changes are not recorded automatically. Use "mdarcs record mdarcs" to record changes when you are finished editing the mdarcs configuration file. See also: 'mdarcs help folder record' for organizing modules. EOS; } elseif ($subtopic=='edit') { echo << [-v] Arguments: the mdarcs module to modify Options: -v verbose Data is saved to a temporary file. Suspend mdarcs and edit the information. One line per item. Do not change the descriptive prefix. Return to mdarcs when done. Note that changes are not recorded automatically. Use "mdarcs record mdarcs" to record changes when you are finished editing the mdarcs configuration file. Changes to module definitions are NOT retroactive. EOS; } elseif ($subtopic=='show') { echo <<] [-a] [-v] Arguments: the mdarcs module to show Options: -a All. Shows all defined modules, even if they don't have files. -v Verbose. Shows regular expression used to match module files and directories. Displays the description (and optionally the regular expression) for mdarcs module(s). By default only the modules with files in the local repository are listed. EOS; } elseif ($subtopic=='test') { echo << ... A set of tools used to simplify creating, editing, and testing mdarcs module definitions. See help on the subcommands for more information. EOS; } } elseif ($topic=='whatsnew') { echo <<] Arguments: the mdarcs module in which to list changes Displays changes to files in the selected mdarcs module. If the module is omitted, whatsnew lists the modules with changes. Also use 'darcs whatsnew' for an overview of changes. EOS; } elseif ($topic=='folder') { echo << [-a] Arguments: the mdarcs module in which to record changes Options: -a select all changes in the module for recording Record changes in the specified module. Unlike darcs, the patch name and long comment are requested fist (so it's a good idea to know what you plan to record). Changes can then be selected interactively for recording. Use the "-a" option if you know you want to record all changes. While editing the long comment, make sure not to modify the patch name prefix. The patch name prefix is used to identify the module in which the patch had been recorded. To skip the long comment, just press return when given the option to suspend mdarcs. EOS; } elseif ($topic=='tag') { echo << [--stable] Arguments: the mdarcs module in which to record a tag Options: --stable mark this tag as stable Tag the current set of patches in the specified mdarcs module. Tags may be marked as identifying a stable module state. (These stable tags can later be used by 'mdarcs pull' and 'mdarcs pullall'.) Unlike darcs, 'mdarcs tag' tags a subset of the repository and allows long comments. To achieve this, a specialized tag is "forged" in mdarcs and applied to the repository with 'darcs apply'. EOS; } elseif ($topic=='apply') { echo << Arguments: a patch bundle generated by "darcs send" Options: -v show details for all patches (not just bad patches) --preview check patches without applying them Apply scans the patch file to ensure that all patches satisfy the modularity requirements of valid mdarcs patches. If the patch files passes, it is then applied to the underlying darcs repository. New patches should only be added to an mdarcs repository through "mdarcs apply" (or pulled from a repository that does so). Always send and apply patches that modify the "mdarcs" module first (separately). These patches modify the mdarcs configuration file, which must be up to date for other patches to pass the apply command tests. EOS; } elseif ($topic=='pull') { echo << [--stable] [--preview] [] Arguments: module from which to pull changes remote repository to pull from, otherwise most recent Options: --stable only pull up to the most recent stable tag --preview just show what would be pulled Pull in changes to just one mdarcs module. Use the "--stable" option if you do not want to pull in changes that are not yet tagged as stable. See "mdarcs pullall" if you want to pull changes to more than one module at once. EOS; } elseif ($topic=='pullall') { echo <<] Arguments: remote repository to pull from, otherwise most recent Options: --update only pull from modules you already have --stable only pull up to the most recent stable tag --preview just show what would be pulled Pull in changes to multiple mdarcs modules. Use "--update" to only pull from modules for which you already have some files. This is done by scanning the working directory of your repository and may occasionally produce false positives. Otherwise, pullall will pull in all modules defined in your repository. Use the "--stable" option if you do not want to pull in changes that are not yet tagged as stable. Note: "mdarcs pullall" is not the same as "darcs pull -a" (which will also pull patches not recorded with mdarcs). EOS; } elseif ($topic=='convert') { echo << array("pipe", "r"), // stdin is a pipe that the child will read from 1 => array("pipe", "w"), // stdout is a pipe that the child will write to 2 => array("pipe", "w") // stderr is a pipe that the child will write to ); $cwd = null; $env = array(); $process = proc_open($darcspath.' --version', $descriptorspec, $pipes, $cwd, $env); if (!is_resource($process)) { echo "Can't run darcs!\n"; exit(); } // $pipes now looks like this: // 0 => writeable handle connected to child stdin // 1 => readable handle connected to child stdout // 2 => readable handle connected to child stderr fclose($pipes[0]); $darcs_version = trim(fgets($pipes[1])); $err = fgets($pipes[2]); // close pipes before proc_close fclose($pipes[1]); fclose($pipes[2]); proc_close($process); if (strpos($err, 'not found')!==false) { $darcs_version = "command '{$darcspath}' not found"; } $conf = load_conf(true); if ($conf!==false) { $mdarcs_repo_version = $conf['meta']['saved-with-version']; } // check versions $version_errors = array(); if ($conf!==false && compare_numeric_version($version, $conf['meta']['saved-with-version'])<0) { $version_errors[] = "WARNING: Repository expects a newer version of mdarcs ({$conf['meta']['saved-with-version']}).\n"; } if ($conf!==false && compare_numeric_version($conf['meta']['saved-with-version'], $version_prefs_min)<0) { $version_errors[] = "ERROR: Repository not compatible with this version of mdarcs. The old mdarcs repository format ({$conf['meta']['saved-with-version']}) needs to be converted.\n"; //FIXME!! ask user, then run conversion // Note: no format changes that need conversion are in the wild yet. } if (compare_numeric_version($darcs_version, $version_darcs_min)>0) { $version_errors[] = "WARNING: Found a darcs version newer than {$version_darcs_min}. This may cause mdarcs to fail!\n"; } if (compare_numeric_version($darcs_version, $version_darcs_min)<0) { $version_errors[] = "ERROR: Old version of darcs found. Requires at least darcs {$version_darcs_min}.\n"; } if (compare_numeric_version(phpversion(), $version_php_min)<0) { $version_errors[] = "ERROR: The installed version of PHP (".phpversion().") is to old to run mdarcs. Please upgrade to PHP version {$version_php_min} or greater.\n"; } // print version information if ($conf!==false) { echo "repository: {$mdarcs_repo_version}\n"; } echo "mdarcs: {$version}\n"; echo "darcs: {$darcs_version}\n"; echo "php: ".phpversion()."\n"; // and version errors if (!empty($version_errors)) { echo "\n"; foreach ($version_errors as $e) { echo $e; } } exit(); } function run_init($from_repo=false) { global $repo_base, $conf_name, $darcspath, $dname, $version; $repo_base = $_SERVER['PWD']; $repo_base = find_repo_base(true); // check if mdarcs conf exists if (file_exists($repo_base.'/'.$conf_name)) { echo "$conf_name already exists!\n"; echo "Use the mdarcs module commands to change the configuration instead.\n"; exit(); } // darcs init (if not in repository) if (!file_exists($repo_base.'/'.$dname)) { shell_exec($darcspath.' init'); $repo_base = find_repo_base(); } else { // REFUSE to init in an existing darcs repository. // Prevents the user from using mdarcs without a proper repo-"conversion". // Conversion is needed to modularize a monolithic repo so mdarcs // semantics actually work! echo "Do not run 'mdarcs init' in an existing darcs repository!\n"; echo "Create an empty directory or modularize the existing repository.\n"; echo "\nSee 'mdarcs help convert'.\n"; exit(); } if ($from_repo===false) { // create containing folder if (!mkdir(dirname($repo_base.'/'.$conf_name), fileperms($repo_base)&511, true)) { echo 'Error directory for mdarcs data in this project!'; exit(); } // build default mdarcs conf $reg_conf_name = str_replace('.', '\.', dirname($conf_name).'/'); //FIXME!! warning, does not escape everything $conf = array(); $conf['meta'] = array(); $conf['meta']['saved-with-version'] = $version; $conf['modules'] = array(); $conf['modules']['main'] = array(); $conf['modules']['main']['filter'] = '#^[^/]*$#'; $conf['modules']['main']['description'] = 'Files at the base of the project.'; $conf['modules']['mdarcs'] = array(); $conf['modules']['mdarcs']['filter'] = '#^'.$reg_conf_name.'[^/]*$#'; $conf['modules']['mdarcs']['description'] = 'Configuration and support files for mdarcs.'; // mdarcs init $f = tempnam($repo_base, $conf_name); if (!file_put_contents($f, json_indent(json_encode($conf))."\n")) { echo 'Error writing mdarcs configuration tmp file.'; unlink($f); exit(); } if (!rename($f, $repo_base.'/'.$conf_name)) { echo 'Error renaming mdarcs configuration tmp file.'; unlink($f); exit(); } // add mdarcs config file to darcs project shell_exec($darcspath.' add '.$conf_name); // reporting echo "New modular repository setup. You should now configure some modules.\n"; } else { // grab module information from specified repo // pre-reporting echo "Retrieving module information. Use 'mdarcs pull' to load some modules.\n\n"; // delegate to pull run_pull('mdarcs', false, false, $from_repo); } exit(); } function run_module_add($module, $path, $recursive, $verbose) { global $conf, $repo_base, $conf_name, $version; // check module name if (isset($conf['modules'][$module])) { echo "Module '{$module}' already exists.\n"; exit(); } if (substr($module, 0, 1)=='_') { // protect magic module names echo "Module name may not begin with '_'!\n"; exit(); } // adjust path $real_base = realpath($repo_base); $tmp = realpath($path); if ($tmp===false) { echo "Bad path: {$path}\n"; echo "Path must exist. Path is relative to the current working directory.\n"; exit(); } else { $path = $tmp; } if (strpos($path, $real_base)!==false) { $path=substr($path, strlen($real_base)); } if (substr($path, 0, 1)==='/') { $path = substr($path, 1); } if (strlen($path)==0) { echo "Empty path. Modules at the repositories root must be configured manually.\n"; exit(); } if (substr($path, -1)!=='/') { // add trailing slash to directory $path .= '/'; } // get description $description = valid_input("Enter module description: \n", '#.+#'); // build filter if ($recursive) { $filter = "#^{$path}#"; } else { $filter = "#^{$path}[^/]*\$#"; } // build module entry $mod = array(); $mod['filter'] = $filter; $mod['description'] = $description; $conf['modules'][$module] = $mod; // resort module list ksort($conf['modules']); // update last saved version $conf['meta']['saved-with-version'] = $version; // save configuration $f = tempnam($repo_base, $conf_name); if (!file_put_contents($f, json_indent(json_encode($conf))."\n")) { echo 'Error writing mdarcs configuration tmp file.'; unlink($f); exit(); } if (!rename($f, $repo_base.'/'.$conf_name)) { echo 'Error renaming mdarcs configuration tmp file.'; unlink($f); exit(); } // test new configuration run_module_test($verbose); exit(); } function run_module_edit($module, $verbose) { global $conf, $repo_base, $conf_name, $version; // module info tmp file $module_tmp = tempnam($_SERVER['PWD'], 'module_'); // save data $mod = $conf['modules'][$module]; $tmp = "filter: {$mod['filter']}\ndescription: {$mod['description']}\n"; file_put_contents($module_tmp, $tmp); // get changes and validate $try_again = true; while ($try_again) { $ok = true; $matches = array(); $mod = array(); // wait for long comment echo "Suspend mdarcs and edit: ".basename($module_tmp)."\n"; echo "Return to mdarcs. Press return to continue...\n"; valid_input('', '#.*#'); //blocks on input // extract and validate $data = file($module_tmp); if (preg_match("#^filter: (.*?)\n\$#", $data[0], $matches)) { $mod['filter']=$matches[1]; } else { $ok=false; } if (preg_match("#^description: (.*?)\n\$#", $data[1], $matches)) { $mod['description']=$matches[1]; } else { $ok=false; } if (strlen($mod['filter'])<3 || strlen($mod['description'])<2) { $ok=false; } // retry if bad? if (!$ok) { if (strtolower(valid_input('Bad edit. Try again? (YN) ', '#^[yYnN]$#'))=='n') { $try_again=false; } } else { $try_again=false; } } unlink($module_tmp); if ($ok) { // update real conf $conf['modules'][$module] = $mod; // update last saved version $conf['meta']['saved-with-version'] = $version; // save configuration $f = tempnam($repo_base, $conf_name); if (!file_put_contents($f, json_indent(json_encode($conf))."\n")) { echo 'Error writing mdarcs configuration tmp file.'; unlink($f); exit(); } if (!rename($f, $repo_base.'/'.$conf_name)) { echo 'Error renaming mdarcs configuration tmp file.'; unlink($f); exit(); } // test new configuration run_module_test($verbose); } exit(); } function run_module_show($module, $all, $verbose) { global $conf; $show_mods = array(); if ($module===false) { if ($all) { // all defined modules $show_mods = array_keys($conf['modules']); echo "Defined modules:\n"; } else { // modules with files in this repo $show_mods = local_mdarcs_modules(); echo "Local modules:\n"; } } else { // specific module $show_mods[] = $module; } // show modules foreach ($show_mods as $mod) { $mod_info = $conf['modules'][$mod]; echo "{$mod}\t{$mod_info['description']}\n"; if ($verbose) { echo "\t{$mod_info['filter']}\n"; } } exit(); } function run_module_test($verbose) { global $repo_base, $conf; $base = $repo_base.'/'; $bclip = strlen($base); $stats = array('paths' => 0, 'missing' => 0, 'multi' => 0); $bad_paths = array(); $dstack = array($base); $d = null; $path = null; $mods = null; echo "Crosschecking files against defined modules...\n"; while(count($dstack)>0) { $d = dir(array_pop($dstack)); while (($path = $d->read())!==false) { if ($path=='.' || $path=='..' || $path=='_darcs') { continue; } $path = substr($d->path.$path, $bclip); if (is_dir($base.$path)) { $path = $path.'/'; array_push($dstack, $base.$path); } $mods = in_modules($path); if (count($mods)===0 || count($mods)>1) { $bad_paths[$path] = $mods; } if ($verbose) { echo "{$path}\t[".implode(', ', $mods)."]\n"; } } $d->close(); } if ($verbose) { echo "\n"; } if (count($bad_paths)===0) { echo "Ok. All files belong to one, and only one module.\n"; } else { ksort($bad_paths); echo "Some paths do not belong to any module '-', or belong to more that one module '+'.\n"; foreach($bad_paths as $path => $mods) { if (count($mods)>0) { echo "\033[31m+\033[0m\t{$path}\t\033[31m[".implode(', ', $mods)."]\033[0m\n"; } else { echo "-\t{$path}\n"; } } } exit(); } function run_whatsnew($module) { global $conf, $darcspath; $descriptorspec = array( 0 => array("pipe", "r"), // stdin is a pipe that the child will read from 1 => array("pipe", "w"), // stdout is a pipe that the child will write to 2 => array("pipe", "w") // stderr is a pipe that the child will write to ); $cwd = null; $env = array(); $env['DARCS_ALWAYS_COLOR'] = 1; //or //$env['DARCS_DONT_COLOR'] = 1; //because term sequences are problematic to parse... $process = proc_open($darcspath.' whatsnew', $descriptorspec, $pipes, $cwd, $env); if (!is_resource($process)) { echo "Can't run darcs!\n"; exit(); } // $pipes now looks like this: // 0 => writeable handle connected to child stdin // 1 => readable handle connected to child stdout // 2 => readable handle connected to child stderr fclose($pipes[0]); $hset_ignore = array('{', '}'); $hset_mod = array('+', '-'); $l = ''; // line $h = ''; // head of line $h2 = ''; // char after color prefix $f = ''; // file less ./ $f2 = ''; // destination of moved file less ./ $passed = false; // file passed filter $no_changes = -1; $changed_mods = array(); // list of modules where changes were found (keys) /// test whatsnew filter while (($l = fgets($pipes[1]))!==false) { if ($no_changes===-1) { if ($no_changes = trim($l)==='No changes!') { break; } } $h = substr($l, 0, 1); $h2 = substr($l, 8, 1); // deals with DARCS_ALWAYS_COLOR if (in_array($h, $hset_mod, true) && !$passed) { continue; } elseif (in_array($h, $hset_mod, true) || in_array($h, $hset_ignore, true) || ($h==="\x1b" && in_array($h2, $hset_ignore, true))) { echo $l; } else { list($h,$f,$f2) = explode(' ', $l, 3); $f = prepare_path($f, $h); $tmp_m1 = in_modules($f); $passed = $tmp_m1[0]===$module; if (count($tmp_m1)===1) { $changed_mods[$tmp_m1[0]]=true; } else { echo "WARNING: Bad module match count! Run 'mdarcs module test'.\n"; } if ($h==='move' || $h==="\033[01;34mmove\033[00m") { // handle 'darcs mv' speacial case $f2 = prepare_path($f2, $h); $tmp_m2 = in_modules($f2); if (count($tmp_m2)===1) { $changed_mods[$tmp_m2[0]]=true; } else { echo "WARNING: Bad module match count! Run 'mdarcs module test'.\n"; } $passed = ($module===$tmp_m1[0] && $module===$tmp_m2[0]); if (!$passed && ($module===$tmp_m1[0] || $module===$tmp_m2[0])) { echo "WARNING: Bad move across module boundary!\n"; echo " Modules: {$tmp_m1[0]} -> {$tmp_m2[0]}\n"; echo " Files: {$f} -> {$f2}\n"; } } //echo '###'.$h.' '.$f.' '.$f2."\n"; if ($passed) { echo $l; } } } if (($module && ($no_changes || !isset($changed_mods[$module]))) || (!$module && count($changed_mods)==0)) { echo "No changes!\n"; } unset($changed_mods[$module]); $changed_mods = array_keys($changed_mods); if (count($changed_mods)>0) { if ($module) { echo "\nChanges in the following other modules:\n"; } else { echo "Changes in the following modules:\n"; } echo implode(', ', $changed_mods)."\n"; } // close pipes before proc_close fclose($pipes[1]); fclose($pipes[2]); proc_close($process); exit(); } function run_folder_record($all) { // Based on run_record, which was written first... global $conf, $darcspath; // make sure the author name is setup get_author(); $patch_name = ''; $did_record = true; $recorded_folders = ''; $descriptorspec = array( 0 => array("pipe", "r"), // stdin is a pipe that the child will read from 1 => array("pipe", "w"), // stdout is a pipe that the child will write to 2 => array("pipe", "w") // stderr is a pipe that the child will write to ); $cwd = null; $env = array(); $env['DARCS_ALWAYS_COLOR'] = 1; //or //$env['DARCS_DONT_COLOR'] = 1; //because term sequences are problematic to parse... while ($did_record) { $did_record = false; $patch_name = ''; $process = proc_open($darcspath.' record --skip-long-comment', $descriptorspec, $pipes, $cwd, $env); if (!is_resource($process)) { echo "Can't run darcs!\n"; exit(); } // $pipes now looks like this: // 0 => writeable handle connected to child stdin // 1 => readable handle connected to child stdout // 2 => readable handle connected to child stderr // ['Record cancelled.', 'Ok, if you don't want to record anything, that's fine!', 'Finished recording patch'] $hset_ignore = array('Record cance', 'Ok, if you d', 'Finished rec'); $q_rec = 'Shall I reco'; // 'Shall I record this change?'... $q_name = 'What is the '; // 'What is the patch name?' $q_long = 'Do you want '; // 'Do you want to add a long comment?' $hset_quiries = array($q_rec, $q_name, $q_long); $hset_mod = array('+', '-'); $l = ''; // line $h = ''; // head of line $h2 = ''; // char after color prefix $h3 = ''; // first 12 chars of line $f = ''; // file less ./ $f2 = ''; // destination of moved file less ./ $passed = false; // file passed filter /// test whatsnew filter while (($h = fgets($pipes[1],2))!==false) { $h3 = ''; if (!in_array($h, $hset_mod, true)) { if (($h3 = fgets($pipes[1],12))!==false) { $h3 = $h.$h3; if (in_array($h3, $hset_quiries, true)) { $l = $h3; } else { if (($l = fgets($pipes[1]))!==false) { $l = $h3.$l; } else { break; } } //echo "###$l\n"; } else { break; } } else { if (($l = fgets($pipes[1]))!==false) { $l = $h.$l; } else { break; } } //echo "#[".var_export($h, true)."]# ".trim($l)."\n"; $h2 = substr($l, 8, 1); // deals with DARCS_ALWAYS_COLOR //$h3 = substr($l, 0, 12); // deals with verbose lines if (in_array($h, $hset_mod, true) && !$passed) { continue; } elseif (in_array($h, $hset_mod, true)) { if (!$all) { echo $l; } } elseif (in_array($h3, $hset_ignore, true)) { // Don't print... we do our own status reporting! //echo $l; } elseif (in_array($h3, $hset_quiries, true)) { //echo "[[[ querie ]]]\n{$h3}\n"; if ($h3===$q_rec) { // record patch? if ($did_record) { // one folder patch at a time! fputs($pipes[0], 'd'); fgets($pipes[1]); //cosume end of query continue; } elseif ($passed) { if ($all) { fputs($pipes[0], 'y'); $did_record = true; } else { $c = valid_input('Shall I record this change? [ynwsdqjk?]: ', '#^[ynwsdqjk?]$#'); $did_record = ($c==='y'); fputs($pipes[0], $c); fgets($pipes[1]); //cosume end of query } } else { fputs($pipes[0], 'n'); fgets($pipes[1]); //cosume end of query } continue; } if ($h3===$q_name) { // patch name, automated //echo "### patch name: {$patch_name}\n"; fputs($pipes[0], $patch_name."\n"); //need to "press return" after entering... fgets($pipes[1]); //cosume end of query // log folder record for status $recorded_folders .= substr($patch_name, 9)."\n"; continue; } } else { // filter list($h,$f,$f2) = explode(' ', $l, 3); $f = prepare_path($f, $h); //echo "### [".count(in_modules($f))."] path: $f \n"; // select only added directories that are not in any defined module $passed = (($h==='adddir' || $h==="\033[01;34madddir\033[00m") && count(in_modules($f))===0); if ($passed && !$did_record) { $patch_name = '_folder: '.$f; } if ($passed && !$all && !$did_record) { echo $l; } } } // close pipes before proc_close fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); } // show status if ($recorded_folders!=='') { echo "Recorded the following folder(s):\n{$recorded_folders}\n"; } else { echo "No folders to record outside modules.\n"; } exit(); } function run_record($module, $all) { global $conf, $darcspath; // make sure the author name is setup get_author(); // long comment tmp file $comment_tmp = tempnam($_SERVER['PWD'], 'comment_'); // patch name? $tmp = valid_input('What is the patch name? ', '#.+#'); $tmp = "{$module}: {$tmp}\n"; file_put_contents($comment_tmp, $tmp); // wait for long comment echo "For a long comment, suspend mdarcs and edit: ".basename($comment_tmp)."\n"; echo "Return to mdarcs. Press return to continue...\n"; valid_input('', '#.*#'); //blocks on input $descriptorspec = array( 0 => array("pipe", "r"), // stdin is a pipe that the child will read from 1 => array("pipe", "w"), // stdout is a pipe that the child will write to 2 => array("pipe", "w") // stderr is a pipe that the child will write to ); $cwd = null; $env = array(); $env['DARCS_ALWAYS_COLOR'] = 1; //or //$env['DARCS_DONT_COLOR'] = 1; //because term sequences are problematic to parse... $process = proc_open($darcspath.' record --logfile=\''.$comment_tmp.'\'', $descriptorspec, $pipes, $cwd, $env); if (!is_resource($process)) { echo "Can't run darcs!\n"; exit(); } // $pipes now looks like this: // 0 => writeable handle connected to child stdin // 1 => readable handle connected to child stdout // 2 => readable handle connected to child stderr // ['Record cancelled.', 'Ok, if you don't want to record anything, that's fine!', 'Finished recording patch'] $hset_ignore = array('Record cance', 'Ok, if you d', 'Finished rec'); $q_rec = 'Shall I reco'; // 'Shall I record this change?'... $q_name = 'What is the '; // 'What is the patch name?' $q_long = 'Do you want '; // 'Do you want to add a long comment?' $hset_quiries = array($q_rec, $q_name, $q_long); $hset_mod = array('+', '-'); $filter = $conf['modules'][$module]['filter']; $l = ''; // line $h = ''; // head of line $h2 = ''; // char after color prefix $h3 = ''; // first 12 chars of line $f = ''; // file less ./ $f2 = ''; // destination of moved file less ./ $passed = false; // file passed filter /// test whatsnew filter while (($h = fgets($pipes[1],2))!==false) { $h3 = ''; if (!in_array($h, $hset_mod, true)) { if (($h3 = fgets($pipes[1],12))!==false) { $h3 = $h.$h3; if (in_array($h3, $hset_quiries, true)) { $l = $h3; } else { if (($l = fgets($pipes[1]))!==false) { $l = $h3.$l; } else { break; } } } else { break; } } else { if (($l = fgets($pipes[1]))!==false) { $l = $h.$l; } else { break; } } //echo "#[".var_export($h, true)."]# ".trim($l)."\n"; $h2 = substr($l, 8, 1); // deals with DARCS_ALWAYS_COLOR //$h3 = substr($l, 0, 12); // deals with verbose lines if (in_array($h, $hset_mod, true) && !$passed) { continue; } elseif (in_array($h, $hset_mod, true)) { if (!$all) { echo $l; } } elseif (in_array($h3, $hset_ignore, true)) { echo $l; } elseif (in_array($h3, $hset_quiries, true)) { if ($h3===$q_rec) { //echo "[[[ querie ]]]\n"; // record patch? if ($passed) { if ($all) { fputs($pipes[0], 'y'); } else { $c = valid_input('Shall I record this change? [ynwsdqjk?]: ', '#^[ynwsdqjk?]$#'); fputs($pipes[0], $c); fgets($pipes[1]); //cosume end of query } } else { fputs($pipes[0], 'n'); fgets($pipes[1]); //cosume end of query } continue; } } else { // filter list($h,$f,$f2) = explode(' ', $l, 3); $f = prepare_path($f, $h); $passed = preg_match($filter, $f)===1; if ($h==='move' || $h==="\033[01;34mmove\033[00m") { // handle 'darcs mv' speacial case $f2 = prepare_path($f2, $h); $tmp_m1 = in_modules($f); $tmp_m2 = in_modules($f2); $passed = ($module===$tmp_m1[0] && $module===$tmp_m2[0]); if (!$passed && ($module===$tmp_m1[0] || $module===$tmp_m2[0])) { echo "WARNING: Bad move across module boundary!\n"; echo " Modules: {$tmp_m1[0]} -> {$tmp_m2[0]}\n"; echo " Files: {$f} -> {$f2}\n"; } } //echo '###'.$h.' '.$f.' '.$f2."\n"; if ($passed && !$all) { echo $l; } } } // close pipes before proc_close fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); unlink($comment_tmp); // long comment file exit(); } function run_tag($module, $stable) { global $conf, $darcspath, $repo_base, $stable_name, $dname; $out_depends = ''; $out_patch = ''; $out_send = ''; $out_stable = ''; $patch_name = ''; $patch_comment = ''; $patch_time = ''; $patch_hash = ''; $patch_author = ''; // set stable $out_stable = $stable?(' '.$stable_name):''; // load author $patch_author = get_author(); //Note: prefix gziped filenames with "compress.zlib://" to decompress on the fly! $deps_map = array(); $scan_tags = array(); $next_inventory = 'hashed_inventory'; $inventory_data = ''; $inventory_matches = ''; $tag_data = ''; $tag_matches = ''; $debug_patchcount = 0; //FIXME!! debug // read inventories for potential dependencies //echo ("Scanning inventories:\n"); //FIXME!! debug while ($next_inventory!==false) { $next_inventory = $repo_base.'/'.$dname.'/'.$next_inventory; if (!file_exists($next_inventory)) { echo "Can't find darcs inventory file!\n".$next_inventory."\n"; exit(); } $inventory_data = file_get_contents('compress.zlib://'.$next_inventory); $inventory_matches = array(); // find next inventory preg_match('/^(?:pristine:\d{10}-[\dabcdef]{64}\s+)?Starting with inventory:\s+(\d{10}-[\dabcdef]{64})\s/', $inventory_data, $inventory_matches); //echo var_export($inventory_matches,1)."\n"; //FIXME!! debug if (isset($inventory_matches[1])) { $next_inventory = 'inventories/'.$inventory_matches[1]; } else { $next_inventory = false; } $inventory_matches = array(); // parse inventory preg_match_all('/(?:\v|^)(\[([^\v]*)\v.*?\])\s+hash: (\d{10}-[\dabcdef]{64})/', $inventory_data, $inventory_matches, PREG_SET_ORDER); //echo (var_export($inventory_matches,1)."\n"); //FIXME!! debug $debug_patchcount += count($inventory_matches); //FIXME!! debug // filter inventory for ($i=count($inventory_matches)-1; $i>=0; $i--) { // reverses order of patches from each inventory if (preg_match('/^(TAG )?'.$module.'[ :]/', $inventory_matches[$i][2])) { $tmp = array(); $tmp[0] = true; // Is this a "head" our tag should depend on? Will be corrected to false as needed when scanning dependencies. $tmp[1] = $inventory_matches[$i][2]; // extra's for reporting & debugging output... //$tmp[2] = $inventory_matches[$i][3]; // don't need file names if we only check tag dependencies... $deps_map[$inventory_matches[$i][1]] = $tmp; // Is this a tag? if (substr($inventory_matches[$i][2], 0, 4)==='TAG ') { $scan_tags[] = $inventory_matches[$i][3]; //echo ("T {$inventory_matches[$i][2]}\n {$inventory_matches[$i][3]}\n"); //FIXME!! debug } else { //echo ("+ {$inventory_matches[$i][2]}\n"); //FIXME!! debug } } else { //echo ("- {$inventory_matches[$i][2]}\n"); //FIXME!! debug } } } $inventory_data = ''; $inventory_matches = ''; // scan dependencies in module // Simplified by only checking tags explicit dependencies. foreach ($scan_tags as $value) { $tag_path = $repo_base.'/'.$dname.'/patches/'.$value; if (!file_exists($tag_path)) { echo "Can't find darcs patch file!\n".$tag_path."\n"; exit(); } $tag_data = file_get_contents('compress.zlib://'.$tag_path); // trim to just dependencies preg_match('/'."\n".'<(.*)>/s', $tag_data, $tag_matches); $tag_data = trim($tag_matches[1]); // split dependencies preg_match_all('/\[.*?\]/s', $tag_data, $tag_matches); // mark patches that are not at the head foreach ($tag_matches[0] as $value) { $deps_map[$value][0] = false; } } //echo ("\nDeps Map:\n"); //FIXME!! debug foreach ($deps_map as $key => $value) { if ($value[0]) { $out_depends .= $key." \n"; //echo ("+ {$value[1]}\n"); //FIXME!! debug } else { //echo ("- {$value[1]}\n"); //FIXME!! debug } } //echo ("\n".var_export($out_depends,1)."\n"); //FIXME!! debug //echo ("\nParsed $debug_patchcount patches.\n"); //FIXME!! debug // get tag name (and comment) $tmp = valid_input("Tag module {$module} with what version string? ", '#.+#'); $patch_name = "TAG {$module}{$out_stable}: {$tmp}"; $patch_comment = ''; //fill the long comment somehow! i.e. tmp file and vi. $patch_time = gmdate("YmdHis"); // long comment tmp file $comment_tmp = tempnam($_SERVER['PWD'], 'comment_'); file_put_contents($comment_tmp, "{$patch_name}\n"); // wait for long comment echo "For a long comment, suspend mdarcs and edit: ".basename($comment_tmp)."\n"; echo "Return to mdarcs. Press return to continue...\n"; valid_input('', '#.*#'); //blocks on input // read comment back in $tmp = file($comment_tmp); $patch_name = trim(array_shift($tmp)); if (count($tmp)>0) { $tmp[0] = ' '.$tmp[0]; $tmp[count($tmp)-1] = rtrim($tmp[count($tmp)-1]); // remove trailing newline $patch_comment = implode(' ', $tmp); } unlink($comment_tmp); // tmp file name $tmp_file = tempnam($repo_base, 'fakepatch_'); // context fail-retry loop $apply_ok = false; $tag_errors = false; $try = 0; while ($apply_ok == false) { //echo "TRY: $try\n"; $try++; //FIXME!! debug // try to fix context if apply failed if ($tag_errors !== false) { $tag_errors = explode("\n", $tag_errors); if (preg_match('#bug in get_extra commuting patches:#', trim($tag_errors[0]))!==1) { echo "Uh oh! We got an unexpected tag application error:\n"; echo implode("\n", $tag_errors); echo "\n\nPatch file {$tmp_file} left for debugging.\n"; exit(); } // parse bad context error $bad_context_local_date = substr(trim($tag_errors[2]), 0, 28); //ex: 'Sun Jun 29 17:02:02 PDT 2008' $bad_context_patch_name = substr(trim($tag_errors[3]), 2); $bad_context_patch_name_clean = escapeshellcmd(strtr($bad_context_patch_name, '\'"', '..')); //FIXME!! might not escepe everything special to regexp's $out_bad_context_patch = ''; $patch_tree = null; // get changes $patches_xml = shell_exec($darcspath.' changes --xml-output -p \''.$bad_context_patch_name_clean.'\''); $patch_tree = simplexml_load_string($patches_xml); if (!isset($patch_tree->patch)) { echo "No patches found for name: {$bad_context_patch_name} \n"; exit(); } // add one non-commuting patch to the context info foreach ($patch_tree->patch as $patch) { echo "\nAdding dependency required by patch commutation...\n"; echo "Name: '{$bad_context_patch_name}'\n"; echo "Local Date: {$bad_context_local_date}\n"; if ($patch->name==$bad_context_patch_name && $patch['local_date']==$bad_context_local_date) { $out_depends .= "[{$patch->name}\n"; $out_depends .= "{$patch['author']}**{$patch['date']}"; if (isset($patch->comment)) { $out_depends .= "\n"; // indent one space $patch->comment = ' '.$patch->comment; //1st line $patch->comment = str_replace("\n", "\n ", $patch->comment); //rest $out_depends .= $patch->comment; $out_depends .= "\n"; } $out_depends .= "] \n"; } } } // build patch $out_patch = ''; if (empty($patch_comment)) { // normal tag $out_patch .= "[{$patch_name}\n"; $out_patch .= "{$patch_author}**{$patch_time}] \n"; $out_patch .= "<\n{$out_depends}>\n"; } else { // long-comment tag $out_patch .= "[{$patch_name}\n"; $out_patch .= "{$patch_author}**{$patch_time}\n"; $out_patch .= "{$patch_comment}\n] \n"; $out_patch .= "<\n{$out_depends}>\n"; } // get patch hash $patch_hash = sha1($out_patch); // build fake 'darcs send' output $out_send = ''; $out_send .= "New patches:\n\n{$out_patch}\n"; $out_send .= "Context:\n\n{$out_depends}"; $out_send .= "Patch bundle hash:\n{$patch_hash}\n"; // save the tmp patch file if (!file_put_contents($tmp_file, $out_send)) { echo "Error writing temporary file: {$tmp_file}\n"; exit(); } // try to apply tag and check for context errors // $pipes now looks like this: // 0 => writeable handle connected to child stdin // 1 => readable handle connected to child stdout // 2 => readable handle connected to child stderr $descriptorspec = array( 0 => array("pipe", "r"), // stdin is a pipe that the child will read from 1 => array("pipe", "w"), // stdout is a pipe that the child will write to 2 => array("pipe", "w") // stderr is a pipe that the child will write to ); $cwd = null; $env = array(); $process = proc_open($darcspath.' apply --no-test --dont-allow-conflicts '.escapeshellcmd($tmp_file), $descriptorspec, $pipes, $cwd, $env); if (!is_resource($process)) { echo "Can't run darcs!\n"; exit(); } // read errors $tmp = ''; $tag_errors = ''; // NOTE: should use false for EOF, but we don't get EOF from this stream when done!? while (($tmp = fread($pipes[2], 8192))!=='') { //echo var_export($tmp)."\n"; //FIXME!! debuging $tag_errors .= $tmp; } if ($tag_errors=='') { $apply_ok = true; $tag_errors = false; } // close pipes before proc_close fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); //echo "\n'{$tag_errors}'\n"; //FIXME!! debugging } // remove tmp file if (!$tag_errors) { unlink($tmp_file); // comment out to retain fake darcs-send file for debugging echo "\nDone tagging.\n"; } exit(); } function audit_magic_patch(&$p) { global $magic_module_names; if (!in_array($p['mod'], $magic_module_names)) { // it's not magic return false; } // _folder // Check that recorded folders are outside other modules // and that we only have folders in this patch. if ($p['mod']=='_folder') { foreach ($p['files'] as $f => &$ok) { $tmp = in_modules($f); $ok = (count($tmp)==0) && $f[strlen($f)-1]=='/'; $p['passed'] = $p['passed'] && $ok===true; } } return true; } function run_apply($patchfile, $verbose, $preview) { global $conf, $darcspath; //echo "### checking: $patchfile \n"; $patches = array(); $patchfile = realpath($patchfile); $pfile = fopen($patchfile, 'rb'); if ($pfile===false) { echo "Error opening patchfile '{$patchfile}'.\n"; exit(); } $h_skip = array( '+', '-', // hunks '*', // binary ' ' // hunk context ); // parse patchfile for relevent info $l = ''; $h = ''; $p = array(); $state = 'start'; while (strlen($l)>0 || ($l = fgets($pfile))!==false) { $l = rtrim($l); //echo "### $l\n"; $h = substr($l, 0, 1); if ($state=='start' && $h=='[') { // module & patch name if (($tmp = strpos($l, ':'))===false) { echo "Error parsing patch, no module name.\n"; echo "Patch: {$l}\n"; fclose($pfile); exit(); } $p['mod'] = substr($l, 1, $tmp-1); $p['name'] = substr($l, $tmp+2); // check for tags if (substr($p['mod'], 0, 4)=='TAG ') { $p['tag'] = true; $p['mod'] = substr($p['mod'], 4); // ignore potential 'stable' flag if it exists $p['mod'] = array_shift(explode(' ', $p['mod'])); } else { $p['tag'] = false; } //echo "### mod: {$p['mod']}\n"; //echo "### name: {$p['name']}\n"; //echo "### is tag: ".var_export($p['tag'], true)."\n\n"; $state = 'user'; $l = ''; continue; } if ($state=='user') { // no long comment (with or wo/hunk) if (preg_match('/^(.*?)\*\*([0-9]*)\] ?(.*)/', $l, $matches)) { // patch submitter info $p['user'] = $matches[1]; //echo "### user: {$p['user']}\n"; $p['deps'] = array(); $p['files'] = array(); $state = 'deps'; if (isset($matches[3])) { if (strlen(trim($matches[3]))>0) { $l = trim($matches[3]); continue; } } } // user for long comment if (preg_match('/^(.*?)\*\*([0-9]*)$/', $l, $matches)) { // patch submitter info $p['user'] = $matches[1]; //echo "### user: {$p['user']}\n"; $p['deps'] = array(); $p['files'] = array(); } // long comment if ($h===' ') { ; // ignored } // end of long comment (with or wo/hunk) if (preg_match('/^\] ?(.*)/', $l, $matches)) { $state = 'deps'; if (isset($matches[1])) { if (strlen(trim($matches[1]))>0) { $l = trim($matches[1]); continue; } } } $l=''; continue; } if ($state=='deps') { if ($l=='<') { // process deps in a subloop $deps_n = -1; while (($l = fgets($pfile))!==false) { $l = rtrim($l); //echo "###### $l\n"; $h = substr($l, 0, 1); if ($h=='>') { $state = 'files'; break; } if ($h=='[') { $deps_n += 1; $p['deps'][$deps_n] = array(); // module & patch name if (($tmp = strpos($l, ':'))===false) { echo "Error parsing patch dependency, no module name.\n"; echo "Patch: {$l}\n"; fclose($pfile); exit(); } $p['deps'][$deps_n]['mod'] = substr($l, 1, $tmp-1); $p['deps'][$deps_n]['name'] = substr($l, $tmp+2); // check for tags if (substr($p['deps'][$deps_n]['mod'], 0, 4)=='TAG ') { $p['deps'][$deps_n]['tag'] = true; $p['deps'][$deps_n]['mod'] = substr($p['deps'][$deps_n]['mod'], 4); // ignore potential 'stable' flag if it exists $p['deps'][$deps_n]['mod'] = array_shift(explode(' ', $p['deps'][$deps_n]['mod'])); } else { $p['deps'][$deps_n]['tag'] = false; } continue; } if (preg_match('/^(.*?)\*\*(.*?)\]/', $l, $matches)) { // submitter info $p['deps'][$deps_n]['user'] = $matches[1]; } } $l = ''; continue; } else { // no deps $state = 'files'; } } if ($state=='files') { // find files touched by this patch if (in_array($h, $h_skip, true)) { // skip patch data $l = ''; continue; } if ($h==='[') { // end of patch $patches[] = $p; $state = 'start'; //echo "\n"; continue; } // stop parsing when there are no more patches if (substr($l, 0, 8)=='Context:') { break; } $tmp = explode(' ', $l); //echo '### '.var_export($tmp, true)."\n"; if (count($tmp)>1) { // found files $f = prepare_path($tmp[1], $tmp[0]); if (!isset($p['files'][$f])) { $p['files'][$f] = false; } if ($tmp[0]==='move') { // handle 'darcs mv' speacial case $f = prepare_path($tmp[2], $tmp[0]); if (!isset($p['files'][$f])) { $p['files'][$f] = false; } } $l = ''; continue; } } $l = ''; } fclose($pfile); //last patch $patches[] = $p; // check modularity of patches foreach ($patches as &$p) { // prepare $p['passed'] = true; $p['warn'] = false; // check magic modules if (audit_magic_patch($p)) { // if it was magic we skip the normal tests continue; } // check patch if (!isset($conf['modules'][$p['mod']])) { // does module exist? $p['mod_unknown'] = true; $p['passed'] = false; } // check dependencies foreach ($p['deps'] as &$d) { if ($d['mod']!==$p['mod']) { $d['bad_dep'] = true; if ($p['tag']) { // Only warn for tags with bad deps // as they may be required for commutation. $p['warn'] = true; } else { // Fail patches with bad deps. $p['passed'] = false; } } } // check files if (isset($p['mod_unknown'])) { // skip when mod is undefined continue; } $filter = $conf['modules'][$p['mod']]['filter']; foreach ($p['files'] as $f => &$ok) { // is path in module? $ok = preg_match($filter, $f)===1; $p['passed'] = $p['passed'] && $ok; if ($ok) { // is path in any other modules? $tmp = in_modules($f); if (count($tmp)!==1) { $ok = $tmp; } $p['passed'] = $p['passed'] && $ok===true; } } } //echo "\n### Patch info:\n".var_export($patches, true)."\n\n"; //FIXME!! debug //FIXME!! even more debugging! //foreach ($patches as &$p) { //echo ($p['tag']?'tagged':'*').' '.$p['mod'].': '.$p['name']."\n"; //} //echo "\n"; //// // report on bad patches $allok = true; $anywarn = false; $show_once = true; foreach ($patches as &$p) { if (!$p['passed'] || $p['warn'] || $verbose) { if ($show_once) { $show_once = false; echo << $isok) { if ($isok===false) { echo "\033[31mx\t{$f}\033[0m\n"; } elseif (is_array($isok)) { echo "\033[31m+\t{$f}\t[".implode(', ', $isok)."]\033[0m\n"; } else { echo "\t{$f}\n"; } } echo "\n"; } } // warn about tags with cross-module deps // (probably just needed for commutation) if ($anywarn) { echo "Some tags depend on other modules. You should verify that these are sane\n"; echo "dependencies caused by nested modules.\n\n"; } // apply the patches if ($allok && !$preview) { // run 'darcs apply' echo "Applying paches...\n"; $descriptorspec = array( 0 => array("pipe", "r"), // stdin is a pipe that the child will read from 1 => array("pipe", "w"), // stdout is a pipe that the child will write to 2 => array("pipe", "w") // stderr is a pipe that the child will write to ); $cwd = null; $env = array(); $env['DARCS_ALWAYS_COLOR'] = 1; //or //$env['DARCS_DONT_COLOR'] = 1; //because term sequences are problematic to parse... $process = proc_open($darcspath.' apply '.$patchfile, $descriptorspec, $pipes, $cwd, $env); if (!is_resource($process)) { echo "Can't run darcs!\n"; exit(); } // $pipes now looks like this: // 0 => writeable handle connected to child stdin // 1 => readable handle connected to child stdout // 2 => readable handle connected to child stderr fclose($pipes[0]); $tmp = ''; // show output from apply while (($tmp = fgets($pipes[1]))!==false) { echo $tmp; } // and stderr just in case while (($tmp = fgets($pipes[2]))!==false) { echo $tmp; } // close pipes before proc_close fclose($pipes[1]); fclose($pipes[2]); proc_close($process); exit(); } else { echo "Nothing applied.\n"; } exit(); } function run_pull($module, $stable, $preview, $remote_repo='') { global $conf, $darcspath, $stable_name; $out_preview = $preview?' --dry-run':''; $out_remote = ($remote_repo!=='')?(' '.$remote_repo):''; $output = ''; if ($stable) { // pull up to last stable tag // darcs pull -s --tags='^main stable:' -a $output = shell_exec($darcspath.' pull -s --tags=\'^'.$module.' '.$stable_name.':\' -a'.$out_preview.$out_remote); } else { // pull everything // darcs pull -s --patches='^(TAG )?main[ :]' -a $output = shell_exec($darcspath.' pull -s --patches=\'^(TAG )?'.$module.'[ :]\' -a'.$out_preview.$out_remote); } echo $output; exit(); } function run_pullall($update, $stable, $preview, $remote_repo='') { global $conf, $repo_base, $darcspath, $stable_name; $pull_mods = array(); $out_preview = $preview?' --dry-run':''; $out_remote = ($remote_repo!=='')?(' '.$remote_repo):''; // build module list if ($update) { // scan repository for modules with existing modules echo "Crosschecking files against defined modules...\n"; $pull_mods = local_mdarcs_modules(); // Confirm selective pullall echo "Found the following modules:\n\t".implode("\n\t",$pull_mods)."\n"; $c = valid_input("Pull these modules".($stable?" up to the latest stable tag":'')."? (Y/N) ", '#^[yYnN]$#'); //blocks on input if (strtolower($c)!='y') { echo "Skipping.\n"; exit(); } } else { // only patches for known modules (not the same as 'darcs pull -a') $pull_mods = array_keys($conf['modules']); } // pull the modules foreach ($pull_mods as $module) { echo "\nPulling module: {$module}\n"; $output = ''; if ($stable) { // pull up to last stable tag // darcs pull -s --tags='^main stable:' -a $output = shell_exec($darcspath.' pull -s --tags=\'^'.$module.' '.$stable_name.':\' -a'.$out_preview.$out_remote); } else { // pull everything // darcs pull -s --patches='^(TAG )?main[ :]' -a $output = shell_exec($darcspath.' pull -s --patches=\'^(TAG )?'.$module.'[ :]\' -a'.$out_preview.$out_remote); } echo $output; } echo "Done.\n"; exit(); }