Merge pull request #4104 from m-martin-78/xfhsupport
[dokuwiki.git] / bin / gittool.php
blob8503be2348573a7645d61571a93ae05e280a717e
1 #!/usr/bin/env php
2 <?php
4 use splitbrain\phpcli\CLI;
5 use splitbrain\phpcli\Options;
7 if (!defined('DOKU_INC')) define('DOKU_INC', realpath(__DIR__ . '/../') . '/');
8 define('NOSESSION', 1);
9 require_once(DOKU_INC . 'inc/init.php');
11 /**
12 * Easily manage DokuWiki git repositories
14 * @author Andreas Gohr <andi@splitbrain.org>
16 class GitToolCLI extends CLI
18 /**
19 * Register options and arguments on the given $options object
21 * @param Options $options
22 * @return void
24 protected function setup(Options $options)
26 $options->setHelp(
27 "Manage git repositories for DokuWiki and its plugins and templates.\n\n" .
28 "$> ./bin/gittool.php clone gallery template:ach\n" .
29 "$> ./bin/gittool.php repos\n" .
30 "$> ./bin/gittool.php origin -v"
33 $options->registerArgument(
34 'command',
35 'Command to execute. See below',
36 true
39 $options->registerCommand(
40 'clone',
41 'Tries to install a known plugin or template (prefix with template:) via git. Uses the DokuWiki.org ' .
42 'plugin repository to find the proper git repository. Multiple extensions can be given as parameters'
44 $options->registerArgument(
45 'extension',
46 'name of the extension to install, prefix with \'template:\' for templates',
47 true,
48 'clone'
51 $options->registerCommand(
52 'install',
53 'The same as clone, but when no git source repository can be found, the extension is installed via ' .
54 'download'
56 $options->registerArgument(
57 'extension',
58 'name of the extension to install, prefix with \'template:\' for templates',
59 true,
60 'install'
63 $options->registerCommand(
64 'repos',
65 'Lists all git repositories found in this DokuWiki installation'
68 $options->registerCommand(
69 '*',
70 'Any unknown commands are assumed to be arguments to git and will be executed in all repositories ' .
71 'found within this DokuWiki installation'
75 /**
76 * Your main program
78 * Arguments and options have been parsed when this is run
80 * @param Options $options
81 * @return void
83 protected function main(Options $options)
85 $command = $options->getCmd();
86 $args = $options->getArgs();
87 if (!$command) $command = array_shift($args);
89 switch ($command) {
90 case '':
91 echo $options->help();
92 break;
93 case 'clone':
94 $this->cmdClone($args);
95 break;
96 case 'install':
97 $this->cmdInstall($args);
98 break;
99 case 'repo':
100 case 'repos':
101 $this->cmdRepos();
102 break;
103 default:
104 $this->cmdGit($command, $args);
109 * Tries to install the given extensions using git clone
111 * @param array $extensions
113 public function cmdClone($extensions)
115 $errors = [];
116 $succeeded = [];
118 foreach ($extensions as $ext) {
119 $repo = $this->getSourceRepo($ext);
121 if (!$repo) {
122 $this->error("could not find a repository for $ext");
123 $errors[] = $ext;
124 } elseif ($this->cloneExtension($ext, $repo)) {
125 $succeeded[] = $ext;
126 } else {
127 $errors[] = $ext;
131 echo "\n";
132 if ($succeeded) $this->success('successfully cloned the following extensions: ' . implode(', ', $succeeded));
133 if ($errors) $this->error('failed to clone the following extensions: ' . implode(', ', $errors));
137 * Tries to install the given extensions using git clone with fallback to install
139 * @param array $extensions
141 public function cmdInstall($extensions)
143 $errors = [];
144 $succeeded = [];
146 foreach ($extensions as $ext) {
147 $repo = $this->getSourceRepo($ext);
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;
156 } elseif ($this->cloneExtension($ext, $repo)) {
157 $succeeded[] = $ext;
158 } else {
159 $errors[] = $ext;
163 echo "\n";
164 if ($succeeded) $this->success('successfully installed the following extensions: ' . implode(', ', $succeeded));
165 if ($errors) $this->error('failed to install the following extensions: ' . implode(', ', $errors));
169 * Executes the given git command in every repository
171 * @param $cmd
172 * @param $arg
174 public function cmdGit($cmd, $arg)
176 $repos = $this->findRepos();
178 $shell = array_merge(['git', $cmd], $arg);
179 $shell = array_map('escapeshellarg', $shell);
180 $shell = implode(' ', $shell);
182 foreach ($repos as $repo) {
183 if (!@chdir($repo)) {
184 $this->error("Could not change into $repo");
185 continue;
188 $this->info("executing $shell in $repo");
189 $ret = 0;
190 system($shell, $ret);
192 if ($ret == 0) {
193 $this->success("git succeeded in $repo");
194 } else {
195 $this->error("git failed in $repo");
201 * Simply lists the repositories
203 public function cmdRepos()
205 $repos = $this->findRepos();
206 foreach ($repos as $repo) {
207 echo "$repo\n";
212 * Install extension from the given download URL
214 * @param string $ext
215 * @return bool|null
217 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");
223 $plugin->setExtension($ext);
225 $url = $plugin->getDownloadURL();
226 if (!$url) {
227 $this->error("no download URL for $ext");
228 return false;
231 $ok = false;
232 try {
233 $this->info("installing $ext via download from $url");
234 $ok = $plugin->installFromURL($url);
235 } catch (Exception $e) {
236 $this->error($e->getMessage());
239 if ($ok) {
240 $this->success("installed $ext via download");
241 return true;
242 } else {
243 $this->success("failed to install $ext via download");
244 return false;
249 * Clones the extension from the given repository
251 * @param string $ext
252 * @param string $repo
253 * @return bool
255 private function cloneExtension($ext, $repo)
257 if (str_starts_with($ext, 'template:')) {
258 $target = fullpath(tpl_incdir() . '../' . substr($ext, 9));
259 } else {
260 $target = DOKU_PLUGIN . $ext;
263 $this->info("cloning $ext from $repo to $target");
264 $ret = 0;
265 system("git clone $repo $target", $ret);
266 if ($ret === 0) {
267 $this->success("cloning of $ext succeeded");
268 return true;
269 } else {
270 $this->error("cloning of $ext failed");
271 return false;
276 * Returns all git repositories in this DokuWiki install
278 * Looks in root, template and plugin directories only.
280 * @return array
282 private function findRepos()
284 $this->info('Looking for .git directories');
285 $data = array_merge(
286 glob(DOKU_INC . '.git', GLOB_ONLYDIR),
287 glob(DOKU_PLUGIN . '*/.git', GLOB_ONLYDIR),
288 glob(fullpath(tpl_incdir() . '../') . '/*/.git', GLOB_ONLYDIR)
291 if (!$data) {
292 $this->error('Found no .git directories');
293 } else {
294 $this->success('Found ' . count($data) . ' .git directories');
296 $data = array_map('fullpath', array_map('dirname', $data));
297 return $data;
301 * Returns the repository for the given extension
303 * @param $extension
304 * @return false|string
306 private function getSourceRepo($extension)
308 /** @var helper_plugin_extension_extension $ext */
309 $ext = plugin_load('helper', 'extension_extension');
310 if (!$ext) die("extension plugin not available, can't continue");
312 $ext->setExtension($extension);
314 $repourl = $ext->getSourcerepoURL();
315 if (!$repourl) return false;
317 // match github repos
318 if (preg_match('/github\.com\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) {
319 $user = $m[1];
320 $repo = $m[2];
321 return 'https://github.com/' . $user . '/' . $repo . '.git';
324 // match gitorious repos
325 if (preg_match('/gitorious.org\/([^\/]+)\/([^\/]+)?/i', $repourl, $m)) {
326 $user = $m[1];
327 $repo = $m[2];
328 if (!$repo) $repo = $user;
330 return 'https://git.gitorious.org/' . $user . '/' . $repo . '.git';
333 // match bitbucket repos - most people seem to use mercurial there though
334 if (preg_match('/bitbucket\.org\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) {
335 $user = $m[1];
336 $repo = $m[2];
337 return 'https://bitbucket.org/' . $user . '/' . $repo . '.git';
340 return false;
344 // Main
345 $cli = new GitToolCLI();
346 $cli->run();