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