1cbfa4829SPhy#!/usr/bin/env php 2c39ae2c9SAndreas Gohr<?php 3cbeaa4a0SAndreas Gohr 4*42469e71SAnna Dabrowskause dokuwiki\plugin\extension\Extension; 51a7e82cdSAnna Dabrowskause dokuwiki\plugin\extension\Installer; 6cbeaa4a0SAndreas Gohruse splitbrain\phpcli\CLI; 7cbeaa4a0SAndreas Gohruse splitbrain\phpcli\Options; 8cbeaa4a0SAndreas Gohr 9b1f206e1SAndreas Gohrif (!defined('DOKU_INC')) define('DOKU_INC', realpath(__DIR__ . '/../') . '/'); 10c39ae2c9SAndreas Gohrdefine('NOSESSION', 1); 11c39ae2c9SAndreas Gohrrequire_once(DOKU_INC . 'inc/init.php'); 12c39ae2c9SAndreas Gohr 13c39ae2c9SAndreas Gohr/** 14c39ae2c9SAndreas Gohr * Easily manage DokuWiki git repositories 15c39ae2c9SAndreas Gohr * 16c39ae2c9SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 17c39ae2c9SAndreas Gohr */ 188c7c53b0SAndreas Gohrclass GitToolCLI extends CLI 198c7c53b0SAndreas Gohr{ 209fb66494SAndreas Gohr /** 219fb66494SAndreas Gohr * Register options and arguments on the given $options object 229fb66494SAndreas Gohr * 23cbeaa4a0SAndreas Gohr * @param Options $options 249fb66494SAndreas Gohr * @return void 259fb66494SAndreas Gohr */ 26d868eb89SAndreas Gohr protected function setup(Options $options) 27d868eb89SAndreas Gohr { 289fb66494SAndreas Gohr $options->setHelp( 299fb66494SAndreas Gohr "Manage git repositories for DokuWiki and its plugins and templates.\n\n" . 309fb66494SAndreas Gohr "$> ./bin/gittool.php clone gallery template:ach\n" . 319fb66494SAndreas Gohr "$> ./bin/gittool.php repos\n" . 32ae1ce4a6SAndreas Gohr "$> ./bin/gittool.php origin -v" 339fb66494SAndreas Gohr ); 34c39ae2c9SAndreas Gohr 359fb66494SAndreas Gohr $options->registerArgument( 369fb66494SAndreas Gohr 'command', 379fb66494SAndreas Gohr 'Command to execute. See below', 389fb66494SAndreas Gohr true 399fb66494SAndreas Gohr ); 40c39ae2c9SAndreas Gohr 419fb66494SAndreas Gohr $options->registerCommand( 429fb66494SAndreas Gohr 'clone', 439fb66494SAndreas Gohr 'Tries to install a known plugin or template (prefix with template:) via git. Uses the DokuWiki.org ' . 449fb66494SAndreas Gohr 'plugin repository to find the proper git repository. Multiple extensions can be given as parameters' 459fb66494SAndreas Gohr ); 469fb66494SAndreas Gohr $options->registerArgument( 479fb66494SAndreas Gohr 'extension', 489fb66494SAndreas Gohr 'name of the extension to install, prefix with \'template:\' for templates', 499fb66494SAndreas Gohr true, 509fb66494SAndreas Gohr 'clone' 519fb66494SAndreas Gohr ); 52c39ae2c9SAndreas Gohr 539fb66494SAndreas Gohr $options->registerCommand( 549fb66494SAndreas Gohr 'install', 559fb66494SAndreas Gohr 'The same as clone, but when no git source repository can be found, the extension is installed via ' . 569fb66494SAndreas Gohr 'download' 579fb66494SAndreas Gohr ); 589fb66494SAndreas Gohr $options->registerArgument( 599fb66494SAndreas Gohr 'extension', 609fb66494SAndreas Gohr 'name of the extension to install, prefix with \'template:\' for templates', 619fb66494SAndreas Gohr true, 629fb66494SAndreas Gohr 'install' 639fb66494SAndreas Gohr ); 64c39ae2c9SAndreas Gohr 659fb66494SAndreas Gohr $options->registerCommand( 669fb66494SAndreas Gohr 'repos', 679fb66494SAndreas Gohr 'Lists all git repositories found in this DokuWiki installation' 689fb66494SAndreas Gohr ); 69c39ae2c9SAndreas Gohr 709fb66494SAndreas Gohr $options->registerCommand( 719fb66494SAndreas Gohr '*', 729fb66494SAndreas Gohr 'Any unknown commands are assumed to be arguments to git and will be executed in all repositories ' . 739fb66494SAndreas Gohr 'found within this DokuWiki installation' 749fb66494SAndreas Gohr ); 75c39ae2c9SAndreas Gohr } 76c39ae2c9SAndreas Gohr 77c39ae2c9SAndreas Gohr /** 789fb66494SAndreas Gohr * Your main program 799fb66494SAndreas Gohr * 809fb66494SAndreas Gohr * Arguments and options have been parsed when this is run 819fb66494SAndreas Gohr * 82cbeaa4a0SAndreas Gohr * @param Options $options 839fb66494SAndreas Gohr * @return void 849fb66494SAndreas Gohr */ 85d868eb89SAndreas Gohr protected function main(Options $options) 86d868eb89SAndreas Gohr { 879fb66494SAndreas Gohr $command = $options->getCmd(); 88cbeaa4a0SAndreas Gohr $args = $options->getArgs(); 89cbeaa4a0SAndreas Gohr if (!$command) $command = array_shift($args); 909fb66494SAndreas Gohr 919fb66494SAndreas Gohr switch ($command) { 929fb66494SAndreas Gohr case '': 931c36b3d8SAndreas Gohr echo $options->help(); 949fb66494SAndreas Gohr break; 959fb66494SAndreas Gohr case 'clone': 962b2d0ba9SAndreas Gohr $this->cmdClone($args); 979fb66494SAndreas Gohr break; 989fb66494SAndreas Gohr case 'install': 992b2d0ba9SAndreas Gohr $this->cmdInstall($args); 1009fb66494SAndreas Gohr break; 1019fb66494SAndreas Gohr case 'repo': 1029fb66494SAndreas Gohr case 'repos': 1032b2d0ba9SAndreas Gohr $this->cmdRepos(); 1049fb66494SAndreas Gohr break; 1059fb66494SAndreas Gohr default: 1062b2d0ba9SAndreas Gohr $this->cmdGit($command, $args); 1079fb66494SAndreas Gohr } 1089fb66494SAndreas Gohr } 1099fb66494SAndreas Gohr 1109fb66494SAndreas Gohr /** 111c39ae2c9SAndreas Gohr * Tries to install the given extensions using git clone 112c39ae2c9SAndreas Gohr * 11342ea7f44SGerrit Uitslag * @param array $extensions 114c39ae2c9SAndreas Gohr */ 115d868eb89SAndreas Gohr public function cmdClone($extensions) 116d868eb89SAndreas Gohr { 117b1f206e1SAndreas Gohr $errors = []; 118b1f206e1SAndreas Gohr $succeeded = []; 119c39ae2c9SAndreas Gohr 120c39ae2c9SAndreas Gohr foreach ($extensions as $ext) { 121c39ae2c9SAndreas Gohr $repo = $this->getSourceRepo($ext); 122c39ae2c9SAndreas Gohr 123c39ae2c9SAndreas Gohr if (!$repo) { 1249fb66494SAndreas Gohr $this->error("could not find a repository for $ext"); 125c39ae2c9SAndreas Gohr $errors[] = $ext; 126b1f206e1SAndreas Gohr } elseif ($this->cloneExtension($ext, $repo)) { 127c39ae2c9SAndreas Gohr $succeeded[] = $ext; 128c39ae2c9SAndreas Gohr } else { 129c39ae2c9SAndreas Gohr $errors[] = $ext; 130c39ae2c9SAndreas Gohr } 131c39ae2c9SAndreas Gohr } 132c39ae2c9SAndreas Gohr 133c39ae2c9SAndreas Gohr echo "\n"; 134b1f206e1SAndreas Gohr if ($succeeded) $this->success('successfully cloned the following extensions: ' . implode(', ', $succeeded)); 135b1f206e1SAndreas Gohr if ($errors) $this->error('failed to clone the following extensions: ' . implode(', ', $errors)); 136c39ae2c9SAndreas Gohr } 137c39ae2c9SAndreas Gohr 138c39ae2c9SAndreas Gohr /** 139c39ae2c9SAndreas Gohr * Tries to install the given extensions using git clone with fallback to install 140c39ae2c9SAndreas Gohr * 14142ea7f44SGerrit Uitslag * @param array $extensions 142c39ae2c9SAndreas Gohr */ 143d868eb89SAndreas Gohr public function cmdInstall($extensions) 144d868eb89SAndreas Gohr { 145b1f206e1SAndreas Gohr $errors = []; 146b1f206e1SAndreas Gohr $succeeded = []; 147c39ae2c9SAndreas Gohr 148c39ae2c9SAndreas Gohr foreach ($extensions as $ext) { 149c39ae2c9SAndreas Gohr $repo = $this->getSourceRepo($ext); 150c39ae2c9SAndreas Gohr 151c39ae2c9SAndreas Gohr if (!$repo) { 1529fb66494SAndreas Gohr $this->info("could not find a repository for $ext"); 1531a7e82cdSAnna Dabrowska 1541a7e82cdSAnna Dabrowska try { 1551a7e82cdSAnna Dabrowska $installer = new Installer(); 1561a7e82cdSAnna Dabrowska $this->info("installing $ext via download"); 1571a7e82cdSAnna Dabrowska $installer->installFromId($ext); 1581a7e82cdSAnna Dabrowska $this->success("installed $ext via download"); 159c39ae2c9SAndreas Gohr $succeeded[] = $ext; 1601a7e82cdSAnna Dabrowska } catch (\Exception $e) { 1611a7e82cdSAnna Dabrowska $this->error("failed to install $ext via download"); 162c39ae2c9SAndreas Gohr $errors[] = $ext; 163c39ae2c9SAndreas Gohr } 164b1f206e1SAndreas Gohr } elseif ($this->cloneExtension($ext, $repo)) { 165c39ae2c9SAndreas Gohr $succeeded[] = $ext; 166c39ae2c9SAndreas Gohr } else { 167c39ae2c9SAndreas Gohr $errors[] = $ext; 168c39ae2c9SAndreas Gohr } 169c39ae2c9SAndreas Gohr } 170c39ae2c9SAndreas Gohr 171c39ae2c9SAndreas Gohr echo "\n"; 172b1f206e1SAndreas Gohr if ($succeeded) $this->success('successfully installed the following extensions: ' . implode(', ', $succeeded)); 173b1f206e1SAndreas Gohr if ($errors) $this->error('failed to install the following extensions: ' . implode(', ', $errors)); 174c39ae2c9SAndreas Gohr } 175c39ae2c9SAndreas Gohr 176c39ae2c9SAndreas Gohr /** 177c39ae2c9SAndreas Gohr * Executes the given git command in every repository 178c39ae2c9SAndreas Gohr * 179c39ae2c9SAndreas Gohr * @param $cmd 180c39ae2c9SAndreas Gohr * @param $arg 181c39ae2c9SAndreas Gohr */ 182d868eb89SAndreas Gohr public function cmdGit($cmd, $arg) 183d868eb89SAndreas Gohr { 184c39ae2c9SAndreas Gohr $repos = $this->findRepos(); 185c39ae2c9SAndreas Gohr 186b1f206e1SAndreas Gohr $shell = array_merge(['git', $cmd], $arg); 187c39ae2c9SAndreas Gohr $shell = array_map('escapeshellarg', $shell); 188b1f206e1SAndreas Gohr $shell = implode(' ', $shell); 189c39ae2c9SAndreas Gohr 190c39ae2c9SAndreas Gohr foreach ($repos as $repo) { 191c39ae2c9SAndreas Gohr if (!@chdir($repo)) { 1929fb66494SAndreas Gohr $this->error("Could not change into $repo"); 193c39ae2c9SAndreas Gohr continue; 194c39ae2c9SAndreas Gohr } 195c39ae2c9SAndreas Gohr 1969fb66494SAndreas Gohr $this->info("executing $shell in $repo"); 197c39ae2c9SAndreas Gohr $ret = 0; 198c39ae2c9SAndreas Gohr system($shell, $ret); 199c39ae2c9SAndreas Gohr 200c39ae2c9SAndreas Gohr if ($ret == 0) { 2019fb66494SAndreas Gohr $this->success("git succeeded in $repo"); 202c39ae2c9SAndreas Gohr } else { 2039fb66494SAndreas Gohr $this->error("git failed in $repo"); 204c39ae2c9SAndreas Gohr } 205c39ae2c9SAndreas Gohr } 206c39ae2c9SAndreas Gohr } 207c39ae2c9SAndreas Gohr 208c39ae2c9SAndreas Gohr /** 209c39ae2c9SAndreas Gohr * Simply lists the repositories 210c39ae2c9SAndreas Gohr */ 211d868eb89SAndreas Gohr public function cmdRepos() 212d868eb89SAndreas Gohr { 213c39ae2c9SAndreas Gohr $repos = $this->findRepos(); 214c39ae2c9SAndreas Gohr foreach ($repos as $repo) { 215c39ae2c9SAndreas Gohr echo "$repo\n"; 216c39ae2c9SAndreas Gohr } 217c39ae2c9SAndreas Gohr } 218c39ae2c9SAndreas Gohr 219c39ae2c9SAndreas Gohr /** 220c39ae2c9SAndreas Gohr * Clones the extension from the given repository 221c39ae2c9SAndreas Gohr * 222c39ae2c9SAndreas Gohr * @param string $ext 223c39ae2c9SAndreas Gohr * @param string $repo 224c39ae2c9SAndreas Gohr * @return bool 225c39ae2c9SAndreas Gohr */ 226d868eb89SAndreas Gohr private function cloneExtension($ext, $repo) 227d868eb89SAndreas Gohr { 2281b2deed9Sfiwswe if (str_starts_with($ext, 'template:')) { 229c39ae2c9SAndreas Gohr $target = fullpath(tpl_incdir() . '../' . substr($ext, 9)); 230c39ae2c9SAndreas Gohr } else { 231c39ae2c9SAndreas Gohr $target = DOKU_PLUGIN . $ext; 232c39ae2c9SAndreas Gohr } 233c39ae2c9SAndreas Gohr 2349fb66494SAndreas Gohr $this->info("cloning $ext from $repo to $target"); 235c39ae2c9SAndreas Gohr $ret = 0; 236c39ae2c9SAndreas Gohr system("git clone $repo $target", $ret); 237c39ae2c9SAndreas Gohr if ($ret === 0) { 2389fb66494SAndreas Gohr $this->success("cloning of $ext succeeded"); 239c39ae2c9SAndreas Gohr return true; 240c39ae2c9SAndreas Gohr } else { 2419fb66494SAndreas Gohr $this->error("cloning of $ext failed"); 242c39ae2c9SAndreas Gohr return false; 243c39ae2c9SAndreas Gohr } 244c39ae2c9SAndreas Gohr } 245c39ae2c9SAndreas Gohr 246c39ae2c9SAndreas Gohr /** 247c39ae2c9SAndreas Gohr * Returns all git repositories in this DokuWiki install 248c39ae2c9SAndreas Gohr * 249c39ae2c9SAndreas Gohr * Looks in root, template and plugin directories only. 250c39ae2c9SAndreas Gohr * 251c39ae2c9SAndreas Gohr * @return array 252c39ae2c9SAndreas Gohr */ 253d868eb89SAndreas Gohr private function findRepos() 254d868eb89SAndreas Gohr { 2559fb66494SAndreas Gohr $this->info('Looking for .git directories'); 256c39ae2c9SAndreas Gohr $data = array_merge( 257c39ae2c9SAndreas Gohr glob(DOKU_INC . '.git', GLOB_ONLYDIR), 258c39ae2c9SAndreas Gohr glob(DOKU_PLUGIN . '*/.git', GLOB_ONLYDIR), 259c39ae2c9SAndreas Gohr glob(fullpath(tpl_incdir() . '../') . '/*/.git', GLOB_ONLYDIR) 260c39ae2c9SAndreas Gohr ); 261c39ae2c9SAndreas Gohr 262c39ae2c9SAndreas Gohr if (!$data) { 2639fb66494SAndreas Gohr $this->error('Found no .git directories'); 264c39ae2c9SAndreas Gohr } else { 2659fb66494SAndreas Gohr $this->success('Found ' . count($data) . ' .git directories'); 266c39ae2c9SAndreas Gohr } 267b0c7084fSAndreas Gohr $data = array_map('fullpath', array_map('dirname', $data)); 268c39ae2c9SAndreas Gohr return $data; 269c39ae2c9SAndreas Gohr } 270c39ae2c9SAndreas Gohr 271c39ae2c9SAndreas Gohr /** 272c39ae2c9SAndreas Gohr * Returns the repository for the given extension 273c39ae2c9SAndreas Gohr * 2741a7e82cdSAnna Dabrowska * @param string $extensionId 27542ea7f44SGerrit Uitslag * @return false|string 276c39ae2c9SAndreas Gohr */ 2771a7e82cdSAnna Dabrowska private function getSourceRepo($extensionId) 278d868eb89SAndreas Gohr { 2791a7e82cdSAnna Dabrowska $extension = Extension::createFromId($extensionId); 2807e8500eeSGerrit Uitslag 2811a7e82cdSAnna Dabrowska $repourl = $extension->getSourcerepoURL(); 28268cf024bSAndreas Gohr if (!$repourl) return false; 28368cf024bSAndreas Gohr 284c39ae2c9SAndreas Gohr // match github repos 28568cf024bSAndreas Gohr if (preg_match('/github\.com\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) { 286c39ae2c9SAndreas Gohr $user = $m[1]; 287c39ae2c9SAndreas Gohr $repo = $m[2]; 288c39ae2c9SAndreas Gohr return 'https://github.com/' . $user . '/' . $repo . '.git'; 289c39ae2c9SAndreas Gohr } 290c39ae2c9SAndreas Gohr 291c39ae2c9SAndreas Gohr // match gitorious repos 29268cf024bSAndreas Gohr if (preg_match('/gitorious.org\/([^\/]+)\/([^\/]+)?/i', $repourl, $m)) { 293c39ae2c9SAndreas Gohr $user = $m[1]; 294c39ae2c9SAndreas Gohr $repo = $m[2]; 295c39ae2c9SAndreas Gohr if (!$repo) $repo = $user; 296c39ae2c9SAndreas Gohr 297c39ae2c9SAndreas Gohr return 'https://git.gitorious.org/' . $user . '/' . $repo . '.git'; 298c39ae2c9SAndreas Gohr } 299c39ae2c9SAndreas Gohr 300c39ae2c9SAndreas Gohr // match bitbucket repos - most people seem to use mercurial there though 30168cf024bSAndreas Gohr if (preg_match('/bitbucket\.org\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) { 302c39ae2c9SAndreas Gohr $user = $m[1]; 303c39ae2c9SAndreas Gohr $repo = $m[2]; 304c39ae2c9SAndreas Gohr return 'https://bitbucket.org/' . $user . '/' . $repo . '.git'; 305c39ae2c9SAndreas Gohr } 306c39ae2c9SAndreas Gohr 307c39ae2c9SAndreas Gohr return false; 308c39ae2c9SAndreas Gohr } 309c39ae2c9SAndreas Gohr} 310c39ae2c9SAndreas Gohr 311b0b7909bSAndreas Gohr// Main 312b0b7909bSAndreas Gohr$cli = new GitToolCLI(); 313b0b7909bSAndreas Gohr$cli->run(); 314