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