1cbfa4829SPhy#!/usr/bin/env php 2c39ae2c9SAndreas Gohr<?php 3cbeaa4a0SAndreas Gohr 4cbeaa4a0SAndreas Gohruse splitbrain\phpcli\CLI; 5cbeaa4a0SAndreas Gohruse splitbrain\phpcli\Options; 6cbeaa4a0SAndreas Gohr 7b1f206e1SAndreas Gohrif (!defined('DOKU_INC')) define('DOKU_INC', realpath(__DIR__ . '/../') . '/'); 8c39ae2c9SAndreas Gohrdefine('NOSESSION', 1); 9c39ae2c9SAndreas Gohrrequire_once(DOKU_INC . 'inc/init.php'); 10c39ae2c9SAndreas Gohr 11c39ae2c9SAndreas Gohr/** 12c39ae2c9SAndreas Gohr * Easily manage DokuWiki git repositories 13c39ae2c9SAndreas Gohr * 14c39ae2c9SAndreas Gohr * @author Andreas Gohr <andi@splitbrain.org> 15c39ae2c9SAndreas Gohr */ 168c7c53b0SAndreas Gohrclass GitToolCLI extends CLI 178c7c53b0SAndreas Gohr{ 189fb66494SAndreas Gohr /** 199fb66494SAndreas Gohr * Register options and arguments on the given $options object 209fb66494SAndreas Gohr * 21cbeaa4a0SAndreas Gohr * @param Options $options 229fb66494SAndreas Gohr * @return void 239fb66494SAndreas Gohr */ 24d868eb89SAndreas Gohr protected function setup(Options $options) 25d868eb89SAndreas Gohr { 269fb66494SAndreas Gohr $options->setHelp( 279fb66494SAndreas Gohr "Manage git repositories for DokuWiki and its plugins and templates.\n\n" . 289fb66494SAndreas Gohr "$> ./bin/gittool.php clone gallery template:ach\n" . 299fb66494SAndreas Gohr "$> ./bin/gittool.php repos\n" . 30ae1ce4a6SAndreas Gohr "$> ./bin/gittool.php origin -v" 319fb66494SAndreas Gohr ); 32c39ae2c9SAndreas Gohr 339fb66494SAndreas Gohr $options->registerArgument( 349fb66494SAndreas Gohr 'command', 359fb66494SAndreas Gohr 'Command to execute. See below', 369fb66494SAndreas Gohr true 379fb66494SAndreas Gohr ); 38c39ae2c9SAndreas Gohr 399fb66494SAndreas Gohr $options->registerCommand( 409fb66494SAndreas Gohr 'clone', 419fb66494SAndreas Gohr 'Tries to install a known plugin or template (prefix with template:) via git. Uses the DokuWiki.org ' . 429fb66494SAndreas Gohr 'plugin repository to find the proper git repository. Multiple extensions can be given as parameters' 439fb66494SAndreas Gohr ); 449fb66494SAndreas Gohr $options->registerArgument( 459fb66494SAndreas Gohr 'extension', 469fb66494SAndreas Gohr 'name of the extension to install, prefix with \'template:\' for templates', 479fb66494SAndreas Gohr true, 489fb66494SAndreas Gohr 'clone' 499fb66494SAndreas Gohr ); 50c39ae2c9SAndreas Gohr 519fb66494SAndreas Gohr $options->registerCommand( 529fb66494SAndreas Gohr 'install', 539fb66494SAndreas Gohr 'The same as clone, but when no git source repository can be found, the extension is installed via ' . 549fb66494SAndreas Gohr 'download' 559fb66494SAndreas Gohr ); 569fb66494SAndreas Gohr $options->registerArgument( 579fb66494SAndreas Gohr 'extension', 589fb66494SAndreas Gohr 'name of the extension to install, prefix with \'template:\' for templates', 599fb66494SAndreas Gohr true, 609fb66494SAndreas Gohr 'install' 619fb66494SAndreas Gohr ); 62c39ae2c9SAndreas Gohr 639fb66494SAndreas Gohr $options->registerCommand( 649fb66494SAndreas Gohr 'repos', 659fb66494SAndreas Gohr 'Lists all git repositories found in this DokuWiki installation' 669fb66494SAndreas Gohr ); 67c39ae2c9SAndreas Gohr 689fb66494SAndreas Gohr $options->registerCommand( 699fb66494SAndreas Gohr '*', 709fb66494SAndreas Gohr 'Any unknown commands are assumed to be arguments to git and will be executed in all repositories ' . 719fb66494SAndreas Gohr 'found within this DokuWiki installation' 729fb66494SAndreas Gohr ); 73c39ae2c9SAndreas Gohr } 74c39ae2c9SAndreas Gohr 75c39ae2c9SAndreas Gohr /** 769fb66494SAndreas Gohr * Your main program 779fb66494SAndreas Gohr * 789fb66494SAndreas Gohr * Arguments and options have been parsed when this is run 799fb66494SAndreas Gohr * 80cbeaa4a0SAndreas Gohr * @param Options $options 819fb66494SAndreas Gohr * @return void 829fb66494SAndreas Gohr */ 83d868eb89SAndreas Gohr protected function main(Options $options) 84d868eb89SAndreas Gohr { 859fb66494SAndreas Gohr $command = $options->getCmd(); 86cbeaa4a0SAndreas Gohr $args = $options->getArgs(); 87cbeaa4a0SAndreas Gohr if (!$command) $command = array_shift($args); 889fb66494SAndreas Gohr 899fb66494SAndreas Gohr switch ($command) { 909fb66494SAndreas Gohr case '': 911c36b3d8SAndreas Gohr echo $options->help(); 929fb66494SAndreas Gohr break; 939fb66494SAndreas Gohr case 'clone': 942b2d0ba9SAndreas Gohr $this->cmdClone($args); 959fb66494SAndreas Gohr break; 969fb66494SAndreas Gohr case 'install': 972b2d0ba9SAndreas Gohr $this->cmdInstall($args); 989fb66494SAndreas Gohr break; 999fb66494SAndreas Gohr case 'repo': 1009fb66494SAndreas Gohr case 'repos': 1012b2d0ba9SAndreas Gohr $this->cmdRepos(); 1029fb66494SAndreas Gohr break; 1039fb66494SAndreas Gohr default: 1042b2d0ba9SAndreas Gohr $this->cmdGit($command, $args); 1059fb66494SAndreas Gohr } 1069fb66494SAndreas Gohr } 1079fb66494SAndreas Gohr 1089fb66494SAndreas Gohr /** 109c39ae2c9SAndreas Gohr * Tries to install the given extensions using git clone 110c39ae2c9SAndreas Gohr * 11142ea7f44SGerrit Uitslag * @param array $extensions 112c39ae2c9SAndreas Gohr */ 113d868eb89SAndreas Gohr public function cmdClone($extensions) 114d868eb89SAndreas Gohr { 115b1f206e1SAndreas Gohr $errors = []; 116b1f206e1SAndreas Gohr $succeeded = []; 117c39ae2c9SAndreas Gohr 118c39ae2c9SAndreas Gohr foreach ($extensions as $ext) { 119c39ae2c9SAndreas Gohr $repo = $this->getSourceRepo($ext); 120c39ae2c9SAndreas Gohr 121c39ae2c9SAndreas Gohr if (!$repo) { 1229fb66494SAndreas Gohr $this->error("could not find a repository for $ext"); 123c39ae2c9SAndreas Gohr $errors[] = $ext; 124b1f206e1SAndreas Gohr } elseif ($this->cloneExtension($ext, $repo)) { 125c39ae2c9SAndreas Gohr $succeeded[] = $ext; 126c39ae2c9SAndreas Gohr } else { 127c39ae2c9SAndreas Gohr $errors[] = $ext; 128c39ae2c9SAndreas Gohr } 129c39ae2c9SAndreas Gohr } 130c39ae2c9SAndreas Gohr 131c39ae2c9SAndreas Gohr echo "\n"; 132b1f206e1SAndreas Gohr if ($succeeded) $this->success('successfully cloned the following extensions: ' . implode(', ', $succeeded)); 133b1f206e1SAndreas Gohr if ($errors) $this->error('failed to clone the following extensions: ' . implode(', ', $errors)); 134c39ae2c9SAndreas Gohr } 135c39ae2c9SAndreas Gohr 136c39ae2c9SAndreas Gohr /** 137c39ae2c9SAndreas Gohr * Tries to install the given extensions using git clone with fallback to install 138c39ae2c9SAndreas Gohr * 13942ea7f44SGerrit Uitslag * @param array $extensions 140c39ae2c9SAndreas Gohr */ 141d868eb89SAndreas Gohr public function cmdInstall($extensions) 142d868eb89SAndreas Gohr { 143b1f206e1SAndreas Gohr $errors = []; 144b1f206e1SAndreas Gohr $succeeded = []; 145c39ae2c9SAndreas Gohr 146c39ae2c9SAndreas Gohr foreach ($extensions as $ext) { 147c39ae2c9SAndreas Gohr $repo = $this->getSourceRepo($ext); 148c39ae2c9SAndreas Gohr 149c39ae2c9SAndreas Gohr if (!$repo) { 1509fb66494SAndreas Gohr $this->info("could not find a repository for $ext"); 151c39ae2c9SAndreas Gohr if ($this->downloadExtension($ext)) { 152c39ae2c9SAndreas Gohr $succeeded[] = $ext; 153c39ae2c9SAndreas Gohr } else { 154c39ae2c9SAndreas Gohr $errors[] = $ext; 155c39ae2c9SAndreas Gohr } 156b1f206e1SAndreas Gohr } elseif ($this->cloneExtension($ext, $repo)) { 157c39ae2c9SAndreas Gohr $succeeded[] = $ext; 158c39ae2c9SAndreas Gohr } else { 159c39ae2c9SAndreas Gohr $errors[] = $ext; 160c39ae2c9SAndreas Gohr } 161c39ae2c9SAndreas Gohr } 162c39ae2c9SAndreas Gohr 163c39ae2c9SAndreas Gohr echo "\n"; 164b1f206e1SAndreas Gohr if ($succeeded) $this->success('successfully installed the following extensions: ' . implode(', ', $succeeded)); 165b1f206e1SAndreas Gohr if ($errors) $this->error('failed to install the following extensions: ' . implode(', ', $errors)); 166c39ae2c9SAndreas Gohr } 167c39ae2c9SAndreas Gohr 168c39ae2c9SAndreas Gohr /** 169c39ae2c9SAndreas Gohr * Executes the given git command in every repository 170c39ae2c9SAndreas Gohr * 171c39ae2c9SAndreas Gohr * @param $cmd 172c39ae2c9SAndreas Gohr * @param $arg 173c39ae2c9SAndreas Gohr */ 174d868eb89SAndreas Gohr public function cmdGit($cmd, $arg) 175d868eb89SAndreas Gohr { 176c39ae2c9SAndreas Gohr $repos = $this->findRepos(); 177c39ae2c9SAndreas Gohr 178b1f206e1SAndreas Gohr $shell = array_merge(['git', $cmd], $arg); 179c39ae2c9SAndreas Gohr $shell = array_map('escapeshellarg', $shell); 180b1f206e1SAndreas Gohr $shell = implode(' ', $shell); 181c39ae2c9SAndreas Gohr 182c39ae2c9SAndreas Gohr foreach ($repos as $repo) { 183c39ae2c9SAndreas Gohr if (!@chdir($repo)) { 1849fb66494SAndreas Gohr $this->error("Could not change into $repo"); 185c39ae2c9SAndreas Gohr continue; 186c39ae2c9SAndreas Gohr } 187c39ae2c9SAndreas Gohr 1889fb66494SAndreas Gohr $this->info("executing $shell in $repo"); 189c39ae2c9SAndreas Gohr $ret = 0; 190c39ae2c9SAndreas Gohr system($shell, $ret); 191c39ae2c9SAndreas Gohr 192c39ae2c9SAndreas Gohr if ($ret == 0) { 1939fb66494SAndreas Gohr $this->success("git succeeded in $repo"); 194c39ae2c9SAndreas Gohr } else { 1959fb66494SAndreas Gohr $this->error("git failed in $repo"); 196c39ae2c9SAndreas Gohr } 197c39ae2c9SAndreas Gohr } 198c39ae2c9SAndreas Gohr } 199c39ae2c9SAndreas Gohr 200c39ae2c9SAndreas Gohr /** 201c39ae2c9SAndreas Gohr * Simply lists the repositories 202c39ae2c9SAndreas Gohr */ 203d868eb89SAndreas Gohr public function cmdRepos() 204d868eb89SAndreas Gohr { 205c39ae2c9SAndreas Gohr $repos = $this->findRepos(); 206c39ae2c9SAndreas Gohr foreach ($repos as $repo) { 207c39ae2c9SAndreas Gohr echo "$repo\n"; 208c39ae2c9SAndreas Gohr } 209c39ae2c9SAndreas Gohr } 210c39ae2c9SAndreas Gohr 211c39ae2c9SAndreas Gohr /** 212c39ae2c9SAndreas Gohr * Install extension from the given download URL 213c39ae2c9SAndreas Gohr * 214c39ae2c9SAndreas Gohr * @param string $ext 2157e8500eeSGerrit Uitslag * @return bool|null 216c39ae2c9SAndreas Gohr */ 217d868eb89SAndreas Gohr private function downloadExtension($ext) 218d868eb89SAndreas Gohr { 219c39ae2c9SAndreas Gohr /** @var helper_plugin_extension_extension $plugin */ 220c39ae2c9SAndreas Gohr $plugin = plugin_load('helper', 'extension_extension'); 221c39ae2c9SAndreas Gohr if (!$ext) die("extension plugin not available, can't continue"); 2227e8500eeSGerrit Uitslag 223c39ae2c9SAndreas Gohr $plugin->setExtension($ext); 224c39ae2c9SAndreas Gohr 225c39ae2c9SAndreas Gohr $url = $plugin->getDownloadURL(); 226c39ae2c9SAndreas Gohr if (!$url) { 2279fb66494SAndreas Gohr $this->error("no download URL for $ext"); 228c39ae2c9SAndreas Gohr return false; 229c39ae2c9SAndreas Gohr } 230c39ae2c9SAndreas Gohr 231c39ae2c9SAndreas Gohr $ok = false; 232c39ae2c9SAndreas Gohr try { 2339fb66494SAndreas Gohr $this->info("installing $ext via download from $url"); 234c39ae2c9SAndreas Gohr $ok = $plugin->installFromURL($url); 235c39ae2c9SAndreas Gohr } catch (Exception $e) { 2369fb66494SAndreas Gohr $this->error($e->getMessage()); 237c39ae2c9SAndreas Gohr } 238c39ae2c9SAndreas Gohr 239c39ae2c9SAndreas Gohr if ($ok) { 2409fb66494SAndreas Gohr $this->success("installed $ext via download"); 241c39ae2c9SAndreas Gohr return true; 242c39ae2c9SAndreas Gohr } else { 2439fb66494SAndreas Gohr $this->success("failed to install $ext via download"); 244c39ae2c9SAndreas Gohr return false; 245c39ae2c9SAndreas Gohr } 246c39ae2c9SAndreas Gohr } 247c39ae2c9SAndreas Gohr 248c39ae2c9SAndreas Gohr /** 249c39ae2c9SAndreas Gohr * Clones the extension from the given repository 250c39ae2c9SAndreas Gohr * 251c39ae2c9SAndreas Gohr * @param string $ext 252c39ae2c9SAndreas Gohr * @param string $repo 253c39ae2c9SAndreas Gohr * @return bool 254c39ae2c9SAndreas Gohr */ 255d868eb89SAndreas Gohr private function cloneExtension($ext, $repo) 256d868eb89SAndreas Gohr { 257*1b2deed9Sfiwswe if (str_starts_with($ext, 'template:')) { 258c39ae2c9SAndreas Gohr $target = fullpath(tpl_incdir() . '../' . substr($ext, 9)); 259c39ae2c9SAndreas Gohr } else { 260c39ae2c9SAndreas Gohr $target = DOKU_PLUGIN . $ext; 261c39ae2c9SAndreas Gohr } 262c39ae2c9SAndreas Gohr 2639fb66494SAndreas Gohr $this->info("cloning $ext from $repo to $target"); 264c39ae2c9SAndreas Gohr $ret = 0; 265c39ae2c9SAndreas Gohr system("git clone $repo $target", $ret); 266c39ae2c9SAndreas Gohr if ($ret === 0) { 2679fb66494SAndreas Gohr $this->success("cloning of $ext succeeded"); 268c39ae2c9SAndreas Gohr return true; 269c39ae2c9SAndreas Gohr } else { 2709fb66494SAndreas Gohr $this->error("cloning of $ext failed"); 271c39ae2c9SAndreas Gohr return false; 272c39ae2c9SAndreas Gohr } 273c39ae2c9SAndreas Gohr } 274c39ae2c9SAndreas Gohr 275c39ae2c9SAndreas Gohr /** 276c39ae2c9SAndreas Gohr * Returns all git repositories in this DokuWiki install 277c39ae2c9SAndreas Gohr * 278c39ae2c9SAndreas Gohr * Looks in root, template and plugin directories only. 279c39ae2c9SAndreas Gohr * 280c39ae2c9SAndreas Gohr * @return array 281c39ae2c9SAndreas Gohr */ 282d868eb89SAndreas Gohr private function findRepos() 283d868eb89SAndreas Gohr { 2849fb66494SAndreas Gohr $this->info('Looking for .git directories'); 285c39ae2c9SAndreas Gohr $data = array_merge( 286c39ae2c9SAndreas Gohr glob(DOKU_INC . '.git', GLOB_ONLYDIR), 287c39ae2c9SAndreas Gohr glob(DOKU_PLUGIN . '*/.git', GLOB_ONLYDIR), 288c39ae2c9SAndreas Gohr glob(fullpath(tpl_incdir() . '../') . '/*/.git', GLOB_ONLYDIR) 289c39ae2c9SAndreas Gohr ); 290c39ae2c9SAndreas Gohr 291c39ae2c9SAndreas Gohr if (!$data) { 2929fb66494SAndreas Gohr $this->error('Found no .git directories'); 293c39ae2c9SAndreas Gohr } else { 2949fb66494SAndreas Gohr $this->success('Found ' . count($data) . ' .git directories'); 295c39ae2c9SAndreas Gohr } 296b0c7084fSAndreas Gohr $data = array_map('fullpath', array_map('dirname', $data)); 297c39ae2c9SAndreas Gohr return $data; 298c39ae2c9SAndreas Gohr } 299c39ae2c9SAndreas Gohr 300c39ae2c9SAndreas Gohr /** 301c39ae2c9SAndreas Gohr * Returns the repository for the given extension 302c39ae2c9SAndreas Gohr * 303c39ae2c9SAndreas Gohr * @param $extension 30442ea7f44SGerrit Uitslag * @return false|string 305c39ae2c9SAndreas Gohr */ 306d868eb89SAndreas Gohr private function getSourceRepo($extension) 307d868eb89SAndreas Gohr { 308c39ae2c9SAndreas Gohr /** @var helper_plugin_extension_extension $ext */ 309c39ae2c9SAndreas Gohr $ext = plugin_load('helper', 'extension_extension'); 310c39ae2c9SAndreas Gohr if (!$ext) die("extension plugin not available, can't continue"); 3117e8500eeSGerrit Uitslag 312c39ae2c9SAndreas Gohr $ext->setExtension($extension); 313c39ae2c9SAndreas Gohr 31468cf024bSAndreas Gohr $repourl = $ext->getSourcerepoURL(); 31568cf024bSAndreas Gohr if (!$repourl) return false; 31668cf024bSAndreas Gohr 317c39ae2c9SAndreas Gohr // match github repos 31868cf024bSAndreas Gohr if (preg_match('/github\.com\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) { 319c39ae2c9SAndreas Gohr $user = $m[1]; 320c39ae2c9SAndreas Gohr $repo = $m[2]; 321c39ae2c9SAndreas Gohr return 'https://github.com/' . $user . '/' . $repo . '.git'; 322c39ae2c9SAndreas Gohr } 323c39ae2c9SAndreas Gohr 324c39ae2c9SAndreas Gohr // match gitorious repos 32568cf024bSAndreas Gohr if (preg_match('/gitorious.org\/([^\/]+)\/([^\/]+)?/i', $repourl, $m)) { 326c39ae2c9SAndreas Gohr $user = $m[1]; 327c39ae2c9SAndreas Gohr $repo = $m[2]; 328c39ae2c9SAndreas Gohr if (!$repo) $repo = $user; 329c39ae2c9SAndreas Gohr 330c39ae2c9SAndreas Gohr return 'https://git.gitorious.org/' . $user . '/' . $repo . '.git'; 331c39ae2c9SAndreas Gohr } 332c39ae2c9SAndreas Gohr 333c39ae2c9SAndreas Gohr // match bitbucket repos - most people seem to use mercurial there though 33468cf024bSAndreas Gohr if (preg_match('/bitbucket\.org\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) { 335c39ae2c9SAndreas Gohr $user = $m[1]; 336c39ae2c9SAndreas Gohr $repo = $m[2]; 337c39ae2c9SAndreas Gohr return 'https://bitbucket.org/' . $user . '/' . $repo . '.git'; 338c39ae2c9SAndreas Gohr } 339c39ae2c9SAndreas Gohr 340c39ae2c9SAndreas Gohr return false; 341c39ae2c9SAndreas Gohr } 342c39ae2c9SAndreas Gohr} 343c39ae2c9SAndreas Gohr 344b0b7909bSAndreas Gohr// Main 345b0b7909bSAndreas Gohr$cli = new GitToolCLI(); 346b0b7909bSAndreas Gohr$cli->run(); 347