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('fullpath', 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 $repourl = $ext->getSourcerepoURL(); 280 if(!$repourl) return false; 281 282 // match github repos 283 if(preg_match('/github\.com\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) { 284 $user = $m[1]; 285 $repo = $m[2]; 286 return 'https://github.com/'.$user.'/'.$repo.'.git'; 287 } 288 289 // match gitorious repos 290 if(preg_match('/gitorious.org\/([^\/]+)\/([^\/]+)?/i', $repourl, $m)) { 291 $user = $m[1]; 292 $repo = $m[2]; 293 if(!$repo) $repo = $user; 294 295 return 'https://git.gitorious.org/'.$user.'/'.$repo.'.git'; 296 } 297 298 // match bitbucket repos - most people seem to use mercurial there though 299 if(preg_match('/bitbucket\.org\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) { 300 $user = $m[1]; 301 $repo = $m[2]; 302 return 'https://bitbucket.org/'.$user.'/'.$repo.'.git'; 303 } 304 305 return false; 306 } 307 308 /** 309 * Print an error message 310 * 311 * @param $string 312 */ 313 private function msg_error($string) { 314 if($this->color) echo "\033[31m"; // red 315 echo "E: $string\n"; 316 if($this->color) echo "\033[37m"; // reset 317 } 318 319 /** 320 * Print a success message 321 * 322 * @param $string 323 */ 324 private function msg_success($string) { 325 if($this->color) echo "\033[32m"; // green 326 echo "S: $string\n"; 327 if($this->color) echo "\033[37m"; // reset 328 } 329 330 /** 331 * Print an info message 332 * 333 * @param $string 334 */ 335 private function msg_info($string) { 336 if($this->color) echo "\033[36m"; // cyan 337 echo "I: $string\n"; 338 if($this->color) echo "\033[37m"; // reset 339 } 340}