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/** 13 * Easily manage DokuWiki git repositories 14 * 15 * @author Andreas Gohr <andi@splitbrain.org> 16 */ 17class GitToolCLI extends DokuCLI { 18 19 /** 20 * Register options and arguments on the given $options object 21 * 22 * @param DokuCLI_Options $options 23 * @return void 24 */ 25 protected function setup(DokuCLI_Options $options) { 26 $options->setHelp( 27 "Manage git repositories for DokuWiki and its plugins and templates.\n\n" . 28 "EXAMPLE\n\n" . 29 "$> ./bin/gittool.php clone gallery template:ach\n" . 30 "$> ./bin/gittool.php repos\n" . 31 "$> ./bin/gittool.php origin -v\n" 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 DokuCLI_Options $options 82 * @return void 83 */ 84 protected function main(DokuCLI_Options $options) { 85 $command = $options->getCmd(); 86 if(!$command) $command = array_shift($options->args); 87 88 switch($command) { 89 case '': 90 echo $options->help('foo'); 91 break; 92 case 'clone': 93 $this->cmd_clone($options->args); 94 break; 95 case 'install': 96 $this->cmd_install($options->args); 97 break; 98 case 'repo': 99 case 'repos': 100 $this->cmd_repos(); 101 break; 102 default: 103 $this->cmd_git($command, $options->args); 104 } 105 } 106 107 108 /** 109 * Tries to install the given extensions using git clone 110 * 111 * @param $extensions 112 */ 113 public function cmd_clone($extensions) { 114 $errors = array(); 115 $succeeded = array(); 116 117 foreach($extensions as $ext) { 118 $repo = $this->getSourceRepo($ext); 119 120 if(!$repo) { 121 $this->error("could not find a repository for $ext"); 122 $errors[] = $ext; 123 } else { 124 if($this->cloneExtension($ext, $repo)) { 125 $succeeded[] = $ext; 126 } else { 127 $errors[] = $ext; 128 } 129 } 130 } 131 132 echo "\n"; 133 if($succeeded) $this->success('successfully cloned the following extensions: ' . join(', ', $succeeded)); 134 if($errors) $this->error('failed to clone the following extensions: ' . join(', ', $errors)); 135 } 136 137 /** 138 * Tries to install the given extensions using git clone with fallback to install 139 * 140 * @param $extensions 141 */ 142 public function cmd_install($extensions) { 143 $errors = array(); 144 $succeeded = array(); 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 } else { 157 if($this->cloneExtension($ext, $repo)) { 158 $succeeded[] = $ext; 159 } else { 160 $errors[] = $ext; 161 } 162 } 163 } 164 165 echo "\n"; 166 if($succeeded) $this->success('successfully installed the following extensions: ' . join(', ', $succeeded)); 167 if($errors) $this->error('failed to install the following extensions: ' . join(', ', $errors)); 168 } 169 170 /** 171 * Executes the given git command in every repository 172 * 173 * @param $cmd 174 * @param $arg 175 */ 176 public function cmd_git($cmd, $arg) { 177 $repos = $this->findRepos(); 178 179 $shell = array_merge(array('git', $cmd), $arg); 180 $shell = array_map('escapeshellarg', $shell); 181 $shell = join(' ', $shell); 182 183 foreach($repos as $repo) { 184 if(!@chdir($repo)) { 185 $this->error("Could not change into $repo"); 186 continue; 187 } 188 189 echo "\n"; 190 $this->info("executing $shell in $repo"); 191 $ret = 0; 192 system($shell, $ret); 193 194 if($ret == 0) { 195 $this->success("git succeeded in $repo"); 196 } else { 197 $this->error("git failed in $repo"); 198 } 199 } 200 } 201 202 /** 203 * Simply lists the repositories 204 */ 205 public function cmd_repos() { 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 217 */ 218 private function downloadExtension($ext) { 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 $plugin->setExtension($ext); 223 224 $url = $plugin->getDownloadURL(); 225 if(!$url) { 226 $this->error("no download URL for $ext"); 227 return false; 228 } 229 230 $ok = false; 231 try { 232 $this->info("installing $ext via download from $url"); 233 $ok = $plugin->installFromURL($url); 234 } catch(Exception $e) { 235 $this->error($e->getMessage()); 236 } 237 238 if($ok) { 239 $this->success("installed $ext via download"); 240 return true; 241 } else { 242 $this->success("failed to install $ext via download"); 243 return false; 244 } 245 } 246 247 /** 248 * Clones the extension from the given repository 249 * 250 * @param string $ext 251 * @param string $repo 252 * @return bool 253 */ 254 private function cloneExtension($ext, $repo) { 255 if(substr($ext, 0, 9) == 'template:') { 256 $target = fullpath(tpl_incdir() . '../' . substr($ext, 9)); 257 } else { 258 $target = DOKU_PLUGIN . $ext; 259 } 260 261 $this->info("cloning $ext from $repo to $target"); 262 $ret = 0; 263 system("git clone $repo $target", $ret); 264 if($ret === 0) { 265 $this->success("cloning of $ext succeeded"); 266 return true; 267 } else { 268 $this->error("cloning of $ext failed"); 269 return false; 270 } 271 } 272 273 /** 274 * Returns all git repositories in this DokuWiki install 275 * 276 * Looks in root, template and plugin directories only. 277 * 278 * @return array 279 */ 280 private function findRepos() { 281 $this->info('Looking for .git directories'); 282 $data = array_merge( 283 glob(DOKU_INC . '.git', GLOB_ONLYDIR), 284 glob(DOKU_PLUGIN . '*/.git', GLOB_ONLYDIR), 285 glob(fullpath(tpl_incdir() . '../') . '/*/.git', GLOB_ONLYDIR) 286 ); 287 288 if(!$data) { 289 $this->error('Found no .git directories'); 290 } else { 291 $this->success('Found ' . count($data) . ' .git directories'); 292 } 293 $data = array_map('fullpath', array_map('dirname', $data)); 294 return $data; 295 } 296 297 /** 298 * Returns the repository for the given extension 299 * 300 * @param $extension 301 * @return bool|string 302 */ 303 private function getSourceRepo($extension) { 304 /** @var helper_plugin_extension_extension $ext */ 305 $ext = plugin_load('helper', 'extension_extension'); 306 if(!$ext) die("extension plugin not available, can't continue"); 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 340$GitToolCLI = new GitToolCLI(); 341$GitToolCLI->run();