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