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