1#!/usr/bin/php 2<?php 3if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__).'/../').'/'); 4define('NOSESSION', 1); 5require_once(DOKU_INC.'inc/init.php'); 6 7/** 8 * Easily manage DokuWiki git repositories 9 * 10 * @author Andreas Gohr <andi@splitbrain.org> 11 */ 12class GitToolCLI extends DokuCLI { 13 14 /** 15 * Register options and arguments on the given $options object 16 * 17 * @param DokuCLI_Options $options 18 * @return void 19 */ 20 protected function setup(DokuCLI_Options $options) { 21 $options->setHelp( 22 "Manage git repositories for DokuWiki and its plugins and templates.\n\n". 23 "$> ./bin/gittool.php clone gallery template:ach\n". 24 "$> ./bin/gittool.php repos\n". 25 "$> ./bin/gittool.php origin -v" 26 ); 27 28 $options->registerArgument( 29 'command', 30 'Command to execute. See below', 31 true 32 ); 33 34 $options->registerCommand( 35 'clone', 36 'Tries to install a known plugin or template (prefix with template:) via git. Uses the DokuWiki.org '. 37 'plugin repository to find the proper git repository. Multiple extensions can be given as parameters' 38 ); 39 $options->registerArgument( 40 'extension', 41 'name of the extension to install, prefix with \'template:\' for templates', 42 true, 43 'clone' 44 ); 45 46 $options->registerCommand( 47 'install', 48 'The same as clone, but when no git source repository can be found, the extension is installed via '. 49 'download' 50 ); 51 $options->registerArgument( 52 'extension', 53 'name of the extension to install, prefix with \'template:\' for templates', 54 true, 55 'install' 56 ); 57 58 $options->registerCommand( 59 'repos', 60 'Lists all git repositories found in this DokuWiki installation' 61 ); 62 63 $options->registerCommand( 64 '*', 65 'Any unknown commands are assumed to be arguments to git and will be executed in all repositories '. 66 'found within this DokuWiki installation' 67 ); 68 } 69 70 /** 71 * Your main program 72 * 73 * Arguments and options have been parsed when this is run 74 * 75 * @param DokuCLI_Options $options 76 * @return void 77 */ 78 protected function main(DokuCLI_Options $options) { 79 $command = $options->getCmd(); 80 if(!$command) $command = array_shift($options->args); 81 82 switch($command) { 83 case '': 84 echo $options->help(); 85 break; 86 case 'clone': 87 $this->cmd_clone($options->args); 88 break; 89 case 'install': 90 $this->cmd_install($options->args); 91 break; 92 case 'repo': 93 case 'repos': 94 $this->cmd_repos(); 95 break; 96 default: 97 $this->cmd_git($command, $options->args); 98 } 99 } 100 101 /** 102 * Tries to install the given extensions using git clone 103 * 104 * @param array $extensions 105 */ 106 public function cmd_clone($extensions) { 107 $errors = array(); 108 $succeeded = array(); 109 110 foreach($extensions as $ext) { 111 $repo = $this->getSourceRepo($ext); 112 113 if(!$repo) { 114 $this->error("could not find a repository for $ext"); 115 $errors[] = $ext; 116 } else { 117 if($this->cloneExtension($ext, $repo)) { 118 $succeeded[] = $ext; 119 } else { 120 $errors[] = $ext; 121 } 122 } 123 } 124 125 echo "\n"; 126 if($succeeded) $this->success('successfully cloned the following extensions: '.join(', ', $succeeded)); 127 if($errors) $this->error('failed to clone the following extensions: '.join(', ', $errors)); 128 } 129 130 /** 131 * Tries to install the given extensions using git clone with fallback to install 132 * 133 * @param array $extensions 134 */ 135 public function cmd_install($extensions) { 136 $errors = array(); 137 $succeeded = array(); 138 139 foreach($extensions as $ext) { 140 $repo = $this->getSourceRepo($ext); 141 142 if(!$repo) { 143 $this->info("could not find a repository for $ext"); 144 if($this->downloadExtension($ext)) { 145 $succeeded[] = $ext; 146 } else { 147 $errors[] = $ext; 148 } 149 } else { 150 if($this->cloneExtension($ext, $repo)) { 151 $succeeded[] = $ext; 152 } else { 153 $errors[] = $ext; 154 } 155 } 156 } 157 158 echo "\n"; 159 if($succeeded) $this->success('successfully installed the following extensions: '.join(', ', $succeeded)); 160 if($errors) $this->error('failed to install the following extensions: '.join(', ', $errors)); 161 } 162 163 /** 164 * Executes the given git command in every repository 165 * 166 * @param $cmd 167 * @param $arg 168 */ 169 public function cmd_git($cmd, $arg) { 170 $repos = $this->findRepos(); 171 172 $shell = array_merge(array('git', $cmd), $arg); 173 $shell = array_map('escapeshellarg', $shell); 174 $shell = join(' ', $shell); 175 176 foreach($repos as $repo) { 177 if(!@chdir($repo)) { 178 $this->error("Could not change into $repo"); 179 continue; 180 } 181 182 echo "\n"; 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 cmd_repos() { 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();