You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

347 lines
9.8 KiB

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