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