1#!/usr/bin/env php 2<?php 3 4use splitbrain\phpcli\CLI; 5use splitbrain\phpcli\Options; 6 7if(!defined('DOKU_INC')) define('DOKU_INC', realpath(__DIR__ . '/../') . '/'); 8define('NOSESSION', 1); 9require_once(DOKU_INC . 'inc/init.php'); 10 11/** 12 * Easily manage DokuWiki git repositories 13 * 14 * @author Andreas Gohr <andi@splitbrain.org> 15 */ 16class GitToolCLI extends CLI 17{ 18 19 /** 20 * Register options and arguments on the given $options object 21 * 22 * @param Options $options 23 * @return void 24 */ 25 protected function setup(Options $options) { 26 $options->setHelp( 27 "Manage git repositories for DokuWiki and its plugins and templates.\n\n" . 28 "$> ./bin/gittool.php clone gallery template:ach\n" . 29 "$> ./bin/gittool.php repos\n" . 30 "$> ./bin/gittool.php origin -v" 31 ); 32 33 $options->registerArgument( 34 'command', 35 'Command to execute. See below', 36 true 37 ); 38 39 $options->registerCommand( 40 'clone', 41 'Tries to install a known plugin or template (prefix with template:) via git. Uses the DokuWiki.org ' . 42 'plugin repository to find the proper git repository. Multiple extensions can be given as parameters' 43 ); 44 $options->registerArgument( 45 'extension', 46 'name of the extension to install, prefix with \'template:\' for templates', 47 true, 48 'clone' 49 ); 50 51 $options->registerCommand( 52 'install', 53 'The same as clone, but when no git source repository can be found, the extension is installed via ' . 54 'download' 55 ); 56 $options->registerArgument( 57 'extension', 58 'name of the extension to install, prefix with \'template:\' for templates', 59 true, 60 'install' 61 ); 62 63 $options->registerCommand( 64 'repos', 65 'Lists all git repositories found in this DokuWiki installation' 66 ); 67 68 $options->registerCommand( 69 '*', 70 'Any unknown commands are assumed to be arguments to git and will be executed in all repositories ' . 71 'found within this DokuWiki installation' 72 ); 73 } 74 75 /** 76 * Your main program 77 * 78 * Arguments and options have been parsed when this is run 79 * 80 * @param Options $options 81 * @return void 82 */ 83 protected function main(Options $options) { 84 $command = $options->getCmd(); 85 $args = $options->getArgs(); 86 if(!$command) $command = array_shift($args); 87 88 switch($command) { 89 case '': 90 echo $options->help(); 91 break; 92 case 'clone': 93 $this->cmdClone($args); 94 break; 95 case 'install': 96 $this->cmdInstall($args); 97 break; 98 case 'repo': 99 case 'repos': 100 $this->cmdRepos(); 101 break; 102 default: 103 $this->cmdGit($command, $args); 104 } 105 } 106 107 /** 108 * Tries to install the given extensions using git clone 109 * 110 * @param array $extensions 111 */ 112 public function cmdClone($extensions) { 113 $errors = []; 114 $succeeded = []; 115 116 foreach($extensions as $ext) { 117 $repo = $this->getSourceRepo($ext); 118 119 if (!$repo) { 120 $this->error("could not find a repository for $ext"); 121 $errors[] = $ext; 122 } elseif ($this->cloneExtension($ext, $repo)) { 123 $succeeded[] = $ext; 124 } else { 125 $errors[] = $ext; 126 } 127 } 128 129 echo "\n"; 130 if($succeeded) $this->success('successfully cloned the following extensions: ' . implode(', ', $succeeded)); 131 if($errors) $this->error('failed to clone the following extensions: ' . implode(', ', $errors)); 132 } 133 134 /** 135 * Tries to install the given extensions using git clone with fallback to install 136 * 137 * @param array $extensions 138 */ 139 public function cmdInstall($extensions) { 140 $errors = []; 141 $succeeded = []; 142 143 foreach($extensions as $ext) { 144 $repo = $this->getSourceRepo($ext); 145 146 if (!$repo) { 147 $this->info("could not find a repository for $ext"); 148 if($this->downloadExtension($ext)) { 149 $succeeded[] = $ext; 150 } else { 151 $errors[] = $ext; 152 } 153 } elseif ($this->cloneExtension($ext, $repo)) { 154 $succeeded[] = $ext; 155 } else { 156 $errors[] = $ext; 157 } 158 } 159 160 echo "\n"; 161 if($succeeded) $this->success('successfully installed the following extensions: ' . implode(', ', $succeeded)); 162 if($errors) $this->error('failed to install the following extensions: ' . implode(', ', $errors)); 163 } 164 165 /** 166 * Executes the given git command in every repository 167 * 168 * @param $cmd 169 * @param $arg 170 */ 171 public function cmdGit($cmd, $arg) { 172 $repos = $this->findRepos(); 173 174 $shell = array_merge(['git', $cmd], $arg); 175 $shell = array_map('escapeshellarg', $shell); 176 $shell = implode(' ', $shell); 177 178 foreach($repos as $repo) { 179 if(!@chdir($repo)) { 180 $this->error("Could not change into $repo"); 181 continue; 182 } 183 184 $this->info("executing $shell in $repo"); 185 $ret = 0; 186 system($shell, $ret); 187 188 if($ret == 0) { 189 $this->success("git succeeded in $repo"); 190 } else { 191 $this->error("git failed in $repo"); 192 } 193 } 194 } 195 196 /** 197 * Simply lists the repositories 198 */ 199 public function cmdRepos() { 200 $repos = $this->findRepos(); 201 foreach($repos as $repo) { 202 echo "$repo\n"; 203 } 204 } 205 206 /** 207 * Install extension from the given download URL 208 * 209 * @param string $ext 210 * @return bool|null 211 */ 212 private function downloadExtension($ext) { 213 /** @var helper_plugin_extension_extension $plugin */ 214 $plugin = plugin_load('helper', 'extension_extension'); 215 if(!$ext) die("extension plugin not available, can't continue"); 216 217 $plugin->setExtension($ext); 218 219 $url = $plugin->getDownloadURL(); 220 if(!$url) { 221 $this->error("no download URL for $ext"); 222 return false; 223 } 224 225 $ok = false; 226 try { 227 $this->info("installing $ext via download from $url"); 228 $ok = $plugin->installFromURL($url); 229 } catch(Exception $e) { 230 $this->error($e->getMessage()); 231 } 232 233 if($ok) { 234 $this->success("installed $ext via download"); 235 return true; 236 } else { 237 $this->success("failed to install $ext via download"); 238 return false; 239 } 240 } 241 242 /** 243 * Clones the extension from the given repository 244 * 245 * @param string $ext 246 * @param string $repo 247 * @return bool 248 */ 249 private function cloneExtension($ext, $repo) { 250 if(substr($ext, 0, 9) == 'template:') { 251 $target = fullpath(tpl_incdir() . '../' . substr($ext, 9)); 252 } else { 253 $target = DOKU_PLUGIN . $ext; 254 } 255 256 $this->info("cloning $ext from $repo to $target"); 257 $ret = 0; 258 system("git clone $repo $target", $ret); 259 if($ret === 0) { 260 $this->success("cloning of $ext succeeded"); 261 return true; 262 } else { 263 $this->error("cloning of $ext failed"); 264 return false; 265 } 266 } 267 268 /** 269 * Returns all git repositories in this DokuWiki install 270 * 271 * Looks in root, template and plugin directories only. 272 * 273 * @return array 274 */ 275 private function findRepos() { 276 $this->info('Looking for .git directories'); 277 $data = array_merge( 278 glob(DOKU_INC . '.git', GLOB_ONLYDIR), 279 glob(DOKU_PLUGIN . '*/.git', GLOB_ONLYDIR), 280 glob(fullpath(tpl_incdir() . '../') . '/*/.git', GLOB_ONLYDIR) 281 ); 282 283 if(!$data) { 284 $this->error('Found no .git directories'); 285 } else { 286 $this->success('Found ' . count($data) . ' .git directories'); 287 } 288 $data = array_map('fullpath', array_map('dirname', $data)); 289 return $data; 290 } 291 292 /** 293 * Returns the repository for the given extension 294 * 295 * @param $extension 296 * @return false|string 297 */ 298 private function getSourceRepo($extension) { 299 /** @var helper_plugin_extension_extension $ext */ 300 $ext = plugin_load('helper', 'extension_extension'); 301 if(!$ext) die("extension plugin not available, can't continue"); 302 303 $ext->setExtension($extension); 304 305 $repourl = $ext->getSourcerepoURL(); 306 if(!$repourl) return false; 307 308 // match github repos 309 if(preg_match('/github\.com\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) { 310 $user = $m[1]; 311 $repo = $m[2]; 312 return 'https://github.com/' . $user . '/' . $repo . '.git'; 313 } 314 315 // match gitorious repos 316 if(preg_match('/gitorious.org\/([^\/]+)\/([^\/]+)?/i', $repourl, $m)) { 317 $user = $m[1]; 318 $repo = $m[2]; 319 if(!$repo) $repo = $user; 320 321 return 'https://git.gitorious.org/' . $user . '/' . $repo . '.git'; 322 } 323 324 // match bitbucket repos - most people seem to use mercurial there though 325 if(preg_match('/bitbucket\.org\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) { 326 $user = $m[1]; 327 $repo = $m[2]; 328 return 'https://bitbucket.org/' . $user . '/' . $repo . '.git'; 329 } 330 331 return false; 332 } 333} 334 335// Main 336$cli = new GitToolCLI(); 337$cli->run(); 338