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