xref: /dokuwiki/bin/gittool.php (revision cbeaa4a0479ce7023201d4032978899ffaceb187)
1#!/usr/bin/php
2<?php
3
4use splitbrain\phpcli\CLI;
5use splitbrain\phpcli\Options;
6
7if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../') . '/');
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->cmd_clone($args);
93                break;
94            case 'install':
95                $this->cmd_install($args);
96                break;
97            case 'repo':
98            case 'repos':
99                $this->cmd_repos();
100                break;
101            default:
102                $this->cmd_git($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 cmd_clone($extensions) {
112        $errors = array();
113        $succeeded = array();
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            } else {
122                if($this->cloneExtension($ext, $repo)) {
123                    $succeeded[] = $ext;
124                } else {
125                    $errors[] = $ext;
126                }
127            }
128        }
129
130        echo "\n";
131        if($succeeded) $this->success('successfully cloned the following extensions: ' . join(', ', $succeeded));
132        if($errors) $this->error('failed to clone the following extensions: ' . join(', ', $errors));
133    }
134
135    /**
136     * Tries to install the given extensions using git clone with fallback to install
137     *
138     * @param array $extensions
139     */
140    public function cmd_install($extensions) {
141        $errors = array();
142        $succeeded = array();
143
144        foreach($extensions as $ext) {
145            $repo = $this->getSourceRepo($ext);
146
147            if(!$repo) {
148                $this->info("could not find a repository for $ext");
149                if($this->downloadExtension($ext)) {
150                    $succeeded[] = $ext;
151                } else {
152                    $errors[] = $ext;
153                }
154            } else {
155                if($this->cloneExtension($ext, $repo)) {
156                    $succeeded[] = $ext;
157                } else {
158                    $errors[] = $ext;
159                }
160            }
161        }
162
163        echo "\n";
164        if($succeeded) $this->success('successfully installed the following extensions: ' . join(', ', $succeeded));
165        if($errors) $this->error('failed to install the following extensions: ' . join(', ', $errors));
166    }
167
168    /**
169     * Executes the given git command in every repository
170     *
171     * @param $cmd
172     * @param $arg
173     */
174    public function cmd_git($cmd, $arg) {
175        $repos = $this->findRepos();
176
177        $shell = array_merge(array('git', $cmd), $arg);
178        $shell = array_map('escapeshellarg', $shell);
179        $shell = join(' ', $shell);
180
181        foreach($repos as $repo) {
182            if(!@chdir($repo)) {
183                $this->error("Could not change into $repo");
184                continue;
185            }
186
187            echo "\n";
188            $this->info("executing $shell in $repo");
189            $ret = 0;
190            system($shell, $ret);
191
192            if($ret == 0) {
193                $this->success("git succeeded in $repo");
194            } else {
195                $this->error("git failed in $repo");
196            }
197        }
198    }
199
200    /**
201     * Simply lists the repositories
202     */
203    public function cmd_repos() {
204        $repos = $this->findRepos();
205        foreach($repos as $repo) {
206            echo "$repo\n";
207        }
208    }
209
210    /**
211     * Install extension from the given download URL
212     *
213     * @param string $ext
214     * @return bool|null
215     */
216    private function downloadExtension($ext) {
217        /** @var helper_plugin_extension_extension $plugin */
218        $plugin = plugin_load('helper', 'extension_extension');
219        if(!$ext) die("extension plugin not available, can't continue");
220
221        $plugin->setExtension($ext);
222
223        $url = $plugin->getDownloadURL();
224        if(!$url) {
225            $this->error("no download URL for $ext");
226            return false;
227        }
228
229        $ok = false;
230        try {
231            $this->info("installing $ext via download from $url");
232            $ok = $plugin->installFromURL($url);
233        } catch(Exception $e) {
234            $this->error($e->getMessage());
235        }
236
237        if($ok) {
238            $this->success("installed $ext via download");
239            return true;
240        } else {
241            $this->success("failed to install $ext via download");
242            return false;
243        }
244    }
245
246    /**
247     * Clones the extension from the given repository
248     *
249     * @param string $ext
250     * @param string $repo
251     * @return bool
252     */
253    private function cloneExtension($ext, $repo) {
254        if(substr($ext, 0, 9) == 'template:') {
255            $target = fullpath(tpl_incdir() . '../' . substr($ext, 9));
256        } else {
257            $target = DOKU_PLUGIN . $ext;
258        }
259
260        $this->info("cloning $ext from $repo to $target");
261        $ret = 0;
262        system("git clone $repo $target", $ret);
263        if($ret === 0) {
264            $this->success("cloning of $ext succeeded");
265            return true;
266        } else {
267            $this->error("cloning of $ext failed");
268            return false;
269        }
270    }
271
272    /**
273     * Returns all git repositories in this DokuWiki install
274     *
275     * Looks in root, template and plugin directories only.
276     *
277     * @return array
278     */
279    private function findRepos() {
280        $this->info('Looking for .git directories');
281        $data = array_merge(
282            glob(DOKU_INC . '.git', GLOB_ONLYDIR),
283            glob(DOKU_PLUGIN . '*/.git', GLOB_ONLYDIR),
284            glob(fullpath(tpl_incdir() . '../') . '/*/.git', GLOB_ONLYDIR)
285        );
286
287        if(!$data) {
288            $this->error('Found no .git directories');
289        } else {
290            $this->success('Found ' . count($data) . ' .git directories');
291        }
292        $data = array_map('fullpath', array_map('dirname', $data));
293        return $data;
294    }
295
296    /**
297     * Returns the repository for the given extension
298     *
299     * @param $extension
300     * @return false|string
301     */
302    private function getSourceRepo($extension) {
303        /** @var helper_plugin_extension_extension $ext */
304        $ext = plugin_load('helper', 'extension_extension');
305        if(!$ext) die("extension plugin not available, can't continue");
306
307        $ext->setExtension($extension);
308
309        $repourl = $ext->getSourcerepoURL();
310        if(!$repourl) return false;
311
312        // match github repos
313        if(preg_match('/github\.com\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) {
314            $user = $m[1];
315            $repo = $m[2];
316            return 'https://github.com/' . $user . '/' . $repo . '.git';
317        }
318
319        // match gitorious repos
320        if(preg_match('/gitorious.org\/([^\/]+)\/([^\/]+)?/i', $repourl, $m)) {
321            $user = $m[1];
322            $repo = $m[2];
323            if(!$repo) $repo = $user;
324
325            return 'https://git.gitorious.org/' . $user . '/' . $repo . '.git';
326        }
327
328        // match bitbucket repos - most people seem to use mercurial there though
329        if(preg_match('/bitbucket\.org\/([^\/]+)\/([^\/]+)/i', $repourl, $m)) {
330            $user = $m[1];
331            $repo = $m[2];
332            return 'https://bitbucket.org/' . $user . '/' . $repo . '.git';
333        }
334
335        return false;
336    }
337}
338
339// Main
340$cli = new GitToolCLI();
341$cli->run();
342