gittool.php 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. #!/usr/bin/env php
  2. <?php
  3. use splitbrain\phpcli\CLI;
  4. use splitbrain\phpcli\Options;
  5. if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../') . '/');
  6. define('NOSESSION', 1);
  7. require_once(DOKU_INC . 'inc/init.php');
  8. /**
  9. * Easily manage DokuWiki git repositories
  10. *
  11. * @author Andreas Gohr <andi@splitbrain.org>
  12. */
  13. class GitToolCLI extends CLI {
  14. /**
  15. * Register options and arguments on the given $options object
  16. *
  17. * @param Options $options
  18. * @return void
  19. */
  20. protected function setup(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. $options->registerArgument(
  28. 'command',
  29. 'Command to execute. See below',
  30. true
  31. );
  32. $options->registerCommand(
  33. 'clone',
  34. 'Tries to install a known plugin or template (prefix with template:) via git. Uses the DokuWiki.org ' .
  35. 'plugin repository to find the proper git repository. Multiple extensions can be given as parameters'
  36. );
  37. $options->registerArgument(
  38. 'extension',
  39. 'name of the extension to install, prefix with \'template:\' for templates',
  40. true,
  41. 'clone'
  42. );
  43. $options->registerCommand(
  44. 'install',
  45. 'The same as clone, but when no git source repository can be found, the extension is installed via ' .
  46. 'download'
  47. );
  48. $options->registerArgument(
  49. 'extension',
  50. 'name of the extension to install, prefix with \'template:\' for templates',
  51. true,
  52. 'install'
  53. );
  54. $options->registerCommand(
  55. 'repos',
  56. 'Lists all git repositories found in this DokuWiki installation'
  57. );
  58. $options->registerCommand(
  59. '*',
  60. 'Any unknown commands are assumed to be arguments to git and will be executed in all repositories ' .
  61. 'found within this DokuWiki installation'
  62. );
  63. }
  64. /**
  65. * Your main program
  66. *
  67. * Arguments and options have been parsed when this is run
  68. *
  69. * @param Options $options
  70. * @return void
  71. */
  72. protected function main(Options $options) {
  73. $command = $options->getCmd();
  74. $args = $options->getArgs();
  75. if(!$command) $command = array_shift($args);
  76. switch($command) {
  77. case '':
  78. echo $options->help();
  79. break;
  80. case 'clone':
  81. $this->cmdClone($args);
  82. break;
  83. case 'install':
  84. $this->cmdInstall($args);
  85. break;
  86. case 'repo':
  87. case 'repos':
  88. $this->cmdRepos();
  89. break;
  90. default:
  91. $this->cmdGit($command, $args);
  92. }
  93. }
  94. /**
  95. * Tries to install the given extensions using git clone
  96. *
  97. * @param array $extensions
  98. */
  99. public function cmdClone($extensions) {
  100. $errors = array();
  101. $succeeded = array();
  102. foreach($extensions as $ext) {
  103. $repo = $this->getSourceRepo($ext);
  104. if(!$repo) {
  105. $this->error("could not find a repository for $ext");
  106. $errors[] = $ext;
  107. } else {
  108. if($this->cloneExtension($ext, $repo)) {
  109. $succeeded[] = $ext;
  110. } else {
  111. $errors[] = $ext;
  112. }
  113. }
  114. }
  115. echo "\n";
  116. if($succeeded) $this->success('successfully cloned the following extensions: ' . join(', ', $succeeded));
  117. if($errors) $this->error('failed to clone the following extensions: ' . join(', ', $errors));
  118. }
  119. /**
  120. * Tries to install the given extensions using git clone with fallback to install
  121. *
  122. * @param array $extensions
  123. */
  124. public function cmdInstall($extensions) {
  125. $errors = array();
  126. $succeeded = array();
  127. foreach($extensions as $ext) {
  128. $repo = $this->getSourceRepo($ext);
  129. if(!$repo) {
  130. $this->info("could not find a repository for $ext");
  131. if($this->downloadExtension($ext)) {
  132. $succeeded[] = $ext;
  133. } else {
  134. $errors[] = $ext;
  135. }
  136. } else {
  137. if($this->cloneExtension($ext, $repo)) {
  138. $succeeded[] = $ext;
  139. } else {
  140. $errors[] = $ext;
  141. }
  142. }
  143. }
  144. echo "\n";
  145. if($succeeded) $this->success('successfully installed the following extensions: ' . join(', ', $succeeded));
  146. if($errors) $this->error('failed to install the following extensions: ' . join(', ', $errors));
  147. }
  148. /**
  149. * Executes the given git command in every repository
  150. *
  151. * @param $cmd
  152. * @param $arg
  153. */
  154. public function cmdGit($cmd, $arg) {
  155. $repos = $this->findRepos();
  156. $shell = array_merge(array('git', $cmd), $arg);
  157. $shell = array_map('escapeshellarg', $shell);
  158. $shell = join(' ', $shell);
  159. foreach($repos as $repo) {
  160. if(!@chdir($repo)) {
  161. $this->error("Could not change into $repo");
  162. continue;
  163. }
  164. $this->info("executing $shell in $repo");
  165. $ret = 0;
  166. system($shell, $ret);
  167. if($ret == 0) {
  168. $this->success("git succeeded in $repo");
  169. } else {
  170. $this->error("git failed in $repo");
  171. }
  172. }
  173. }
  174. /**
  175. * Simply lists the repositories
  176. */
  177. public function cmdRepos() {
  178. $repos = $this->findRepos();
  179. foreach($repos as $repo) {
  180. echo "$repo\n";
  181. }
  182. }
  183. /**
  184. * Install extension from the given download URL
  185. *
  186. * @param string $ext
  187. * @return bool|null
  188. */
  189. private function downloadExtension($ext) {
  190. /** @var helper_plugin_extension_extension $plugin */
  191. $plugin = plugin_load('helper', 'extension_extension');
  192. if(!$ext) die("extension plugin not available, can't continue");
  193. $plugin->setExtension($ext);
  194. $url = $plugin->getDownloadURL();
  195. if(!$url) {
  196. $this->error("no download URL for $ext");
  197. return false;
  198. }
  199. $ok = false;
  200. try {
  201. $this->info("installing $ext via download from $url");
  202. $ok = $plugin->installFromURL($url);
  203. } catch(Exception $e) {
  204. $this->error($e->getMessage());
  205. }
  206. if($ok) {
  207. $this->success("installed $ext via download");
  208. return true;
  209. } else {
  210. $this->success("failed to install $ext via download");
  211. return false;
  212. }
  213. }
  214. /**
  215. * Clones the extension from the given repository
  216. *
  217. * @param string $ext
  218. * @param string $repo
  219. * @return bool
  220. */
  221. private function cloneExtension($ext, $repo) {
  222. if(substr($ext, 0, 9) == 'template:') {
  223. $target = fullpath(tpl_incdir() . '../' . substr($ext, 9));
  224. } else {
  225. $target = DOKU_PLUGIN . $ext;
  226. }
  227. $this->info("cloning $ext from $repo to $target");
  228. $ret = 0;
  229. system("git clone $repo $target", $ret);
  230. if($ret === 0) {
  231. $this->success("cloning of $ext succeeded");
  232. return true;
  233. } else {
  234. $this->error("cloning of $ext failed");
  235. return false;
  236. }
  237. }
  238. /**
  239. * Returns all git repositories in this DokuWiki install
  240. *
  241. * Looks in root, template and plugin directories only.
  242. *
  243. * @return array
  244. */
  245. private function findRepos() {
  246. $this->info('Looking for .git directories');
  247. $data = array_merge(
  248. glob(DOKU_INC . '.git', GLOB_ONLYDIR),
  249. glob(DOKU_PLUGIN . '*/.git', GLOB_ONLYDIR),
  250. glob(fullpath(tpl_incdir() . '../') . '/*/.git', GLOB_ONLYDIR)
  251. );
  252. if(!$data) {
  253. $this->error('Found no .git directories');
  254. } else {
  255. $this->success('Found ' . count($data) . ' .git directories');
  256. }
  257. $data = array_map('fullpath', array_map('dirname', $data));
  258. return $data;
  259. }
  260. /**
  261. * Returns the repository for the given extension
  262. *
  263. * @param $extension
  264. * @return false|string
  265. */
  266. private function getSourceRepo($extension) {
  267. /** @var helper_plugin_extension_extension $ext */
  268. $ext = plugin_load('helper', 'extension_extension');
  269. if(!$ext) die("extension plugin not available, can't continue");
  270. $ext->setExtension($extension);
  271. $repourl = $ext->getSourcerepoURL();
  272. if(!$repourl) return false;
  273. // match github repos
  274. if(preg_match('/github\.com\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) {
  275. $user = $m[1];
  276. $repo = $m[2];
  277. return 'https://github.com/' . $user . '/' . $repo . '.git';
  278. }
  279. // match gitorious repos
  280. if(preg_match('/gitorious.org\/([^\/]+)\/([^\/]+)?/i', $repourl, $m)) {
  281. $user = $m[1];
  282. $repo = $m[2];
  283. if(!$repo) $repo = $user;
  284. return 'https://git.gitorious.org/' . $user . '/' . $repo . '.git';
  285. }
  286. // match bitbucket repos - most people seem to use mercurial there though
  287. if(preg_match('/bitbucket\.org\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) {
  288. $user = $m[1];
  289. $repo = $m[2];
  290. return 'https://bitbucket.org/' . $user . '/' . $repo . '.git';
  291. }
  292. return false;
  293. }
  294. }
  295. // Main
  296. $cli = new GitToolCLI();
  297. $cli->run();