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