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$GitToolCLI = new GitToolCLI(); 11 12array_shift($argv); 13$command = array_shift($argv); 14 15switch($command) { 16 case '': 17 case 'help': 18 $GitToolCLI->cmd_help(); 19 break; 20 case 'clone': 21 $GitToolCLI->cmd_clone($argv); 22 break; 23 case 'install': 24 $GitToolCLI->cmd_install($argv); 25 break; 26 case 'repo': 27 case 'repos': 28 $GitToolCLI->cmd_repos(); 29 break; 30 default: 31 $GitToolCLI->cmd_git($command, $argv); 32} 33 34/** 35 * Easily manage DokuWiki git repositories 36 * 37 * @author Andreas Gohr <andi@splitbrain.org> 38 */ 39class GitToolCLI { 40 private $color = true; 41 42 public function cmd_help() { 43 echo <<<EOF 44Usage: gittool.php <command> [parameters] 45 46Manage git repositories for DokuWiki and its plugins and templates. 47 48EXAMPLE 49 50$> ./bin/gittool.php clone gallery template:ach 51$> ./bin/gittool.php repos 52$> ./bin/gittool.php origin -v 53 54COMMANDS 55 56help 57 This help screen 58 59clone <extensions> 60 Tries to install a known plugin or template (prefix with template:) via 61 git. Uses the DokuWiki.org plugin repository to find the proper git 62 repository. Multiple extensions can be given as parameters 63 64install <extensions> 65 The same as clone, but when no git source repository can be found, the 66 extension is installed via download 67 68repos 69 Lists all git repositories found in this DokuWiki installation 70 71<any> 72 Any unknown commands are assumed to be arguments to git and will be 73 executed in all repositories found within this DokuWiki installation 74 75EOF; 76 } 77 78 /** 79 * Tries to install the given extensions using git clone 80 * 81 * @param $extensions 82 */ 83 public function cmd_clone($extensions) { 84 $errors = array(); 85 $succeeded = array(); 86 87 foreach($extensions as $ext) { 88 $repo = $this->getSourceRepo($ext); 89 90 if(!$repo) { 91 $this->msg_error("could not find a repository for $ext"); 92 $errors[] = $ext; 93 } else { 94 if($this->cloneExtension($ext, $repo)) { 95 $succeeded[] = $ext; 96 } else { 97 $errors[] = $ext; 98 } 99 } 100 } 101 102 echo "\n"; 103 if($succeeded) $this->msg_success('successfully cloned the following extensions: '.join(', ', $succeeded)); 104 if($errors) $this->msg_error('failed to clone the following extensions: '.join(', ', $errors)); 105 } 106 107 /** 108 * Tries to install the given extensions using git clone with fallback to install 109 * 110 * @param $extensions 111 */ 112 public function cmd_install($extensions) { 113 $errors = array(); 114 $succeeded = array(); 115 116 foreach($extensions as $ext) { 117 $repo = $this->getSourceRepo($ext); 118 119 if(!$repo) { 120 $this->msg_info("could not find a repository for $ext"); 121 if($this->downloadExtension($ext)) { 122 $succeeded[] = $ext; 123 } else { 124 $errors[] = $ext; 125 } 126 } else { 127 if($this->cloneExtension($ext, $repo)) { 128 $succeeded[] = $ext; 129 } else { 130 $errors[] = $ext; 131 } 132 } 133 } 134 135 echo "\n"; 136 if($succeeded) $this->msg_success('successfully installed the following extensions: '.join(', ', $succeeded)); 137 if($errors) $this->msg_error('failed to install the following extensions: '.join(', ', $errors)); 138 } 139 140 /** 141 * Executes the given git command in every repository 142 * 143 * @param $cmd 144 * @param $arg 145 */ 146 public function cmd_git($cmd, $arg) { 147 $repos = $this->findRepos(); 148 149 $shell = array_merge(array('git', $cmd), $arg); 150 $shell = array_map('escapeshellarg', $shell); 151 $shell = join(' ', $shell); 152 153 foreach($repos as $repo) { 154 if(!@chdir($repo)) { 155 $this->msg_error("Could not change into $repo"); 156 continue; 157 } 158 159 echo "\n"; 160 $this->msg_info("executing $shell in $repo"); 161 $ret = 0; 162 system($shell, $ret); 163 164 if($ret == 0) { 165 $this->msg_success("git succeeded in $repo"); 166 } else { 167 $this->msg_error("git failed in $repo"); 168 } 169 } 170 } 171 172 /** 173 * Simply lists the repositories 174 */ 175 public function cmd_repos() { 176 $repos = $this->findRepos(); 177 foreach($repos as $repo) { 178 echo "$repo\n"; 179 } 180 } 181 182 /** 183 * Install extension from the given download URL 184 * 185 * @param string $ext 186 * @return bool 187 */ 188 private function downloadExtension($ext) { 189 /** @var helper_plugin_extension_extension $plugin */ 190 $plugin = plugin_load('helper', 'extension_extension'); 191 if(!$ext) die("extension plugin not available, can't continue"); 192 $plugin->setExtension($ext); 193 194 $url = $plugin->getDownloadURL(); 195 if(!$url) { 196 $this->msg_error("no download URL for $ext"); 197 return false; 198 } 199 200 $ok = false; 201 try { 202 $this->msg_info("installing $ext via download from $url"); 203 $ok = $plugin->installFromURL($url); 204 } catch(Exception $e) { 205 $this->msg_error($e->getMessage()); 206 } 207 208 if($ok) { 209 $this->msg_success("installed $ext via download"); 210 return true; 211 } else { 212 $this->msg_success("failed to install $ext via download"); 213 return false; 214 } 215 } 216 217 /** 218 * Clones the extension from the given repository 219 * 220 * @param string $ext 221 * @param string $repo 222 * @return bool 223 */ 224 private function cloneExtension($ext, $repo) { 225 if(substr($ext, 0, 9) == 'template:') { 226 $target = fullpath(tpl_incdir().'../'.substr($ext, 9)); 227 } else { 228 $target = DOKU_PLUGIN.$ext; 229 } 230 231 $this->msg_info("cloning $ext from $repo to $target"); 232 $ret = 0; 233 system("git clone $repo $target", $ret); 234 if($ret === 0) { 235 $this->msg_success("cloning of $ext succeeded"); 236 return true; 237 } else { 238 $this->msg_error("cloning of $ext failed"); 239 return false; 240 } 241 } 242 243 /** 244 * Returns all git repositories in this DokuWiki install 245 * 246 * Looks in root, template and plugin directories only. 247 * 248 * @return array 249 */ 250 private function findRepos() { 251 $this->msg_info('Looking for .git directories'); 252 $data = array_merge( 253 glob(DOKU_INC.'.git', GLOB_ONLYDIR), 254 glob(DOKU_PLUGIN.'*/.git', GLOB_ONLYDIR), 255 glob(fullpath(tpl_incdir().'../').'/*/.git', GLOB_ONLYDIR) 256 ); 257 258 if(!$data) { 259 $this->msg_error('Found no .git directories'); 260 } else { 261 $this->msg_success('Found '.count($data).' .git directories'); 262 } 263 $data = array_map('dirname', $data); 264 return $data; 265 } 266 267 /** 268 * Returns the repository for the given extension 269 * 270 * @param $extension 271 * @return bool|string 272 */ 273 private function getSourceRepo($extension) { 274 /** @var helper_plugin_extension_extension $ext */ 275 $ext = plugin_load('helper', 'extension_extension'); 276 if(!$ext) die("extension plugin not available, can't continue"); 277 $ext->setExtension($extension); 278 279 // match github repos 280 if(preg_match('/github\.com\/([^\/]+)\/([^\/]+)/i', $ext->getSourcerepoURL(), $m)) { 281 $user = $m[1]; 282 $repo = $m[2]; 283 return 'https://github.com/'.$user.'/'.$repo.'.git'; 284 } 285 286 // match gitorious repos 287 if(preg_match('/gitorious.org\/([^\/]+)\/([^\/]+)?/i', $ext->getSourcerepoURL(), $m)) { 288 $user = $m[1]; 289 $repo = $m[2]; 290 if(!$repo) $repo = $user; 291 292 return 'https://git.gitorious.org/'.$user.'/'.$repo.'.git'; 293 } 294 295 // match bitbucket repos - most people seem to use mercurial there though 296 if(preg_match('/bitbucket\.org\/([^\/]+)\/([^\/]+)/i', $ext->getSourcerepoURL(), $m)) { 297 $user = $m[1]; 298 $repo = $m[2]; 299 return 'https://bitbucket.org/'.$user.'/'.$repo.'.git'; 300 } 301 302 return false; 303 } 304 305 /** 306 * Print an error message 307 * 308 * @param $string 309 */ 310 private function msg_error($string) { 311 if($this->color) echo "\033[31m"; // red 312 echo "E: $string\n"; 313 if($this->color) echo "\033[37m"; // reset 314 } 315 316 /** 317 * Print a success message 318 * 319 * @param $string 320 */ 321 private function msg_success($string) { 322 if($this->color) echo "\033[32m"; // green 323 echo "S: $string\n"; 324 if($this->color) echo "\033[37m"; // reset 325 } 326 327 /** 328 * Print an info message 329 * 330 * @param $string 331 */ 332 private function msg_info($string) { 333 if($this->color) echo "\033[36m"; // cyan 334 echo "I: $string\n"; 335 if($this->color) echo "\033[37m"; // reset 336 } 337}