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