xref: /dokuwiki/bin/gittool.php (revision 9fb664942d6f51d48d02b592980455259907d9a5)
1#!/usr/bin/php
2<?php
3
4if('cli' != php_sapi_name()) die();
5ini_set('memory_limit', '128M');
6if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../') . '/');
7define('NOSESSION', 1);
8require_once(DOKU_INC . 'inc/init.php');
9
10
11
12/**
13 * Easily manage DokuWiki git repositories
14 *
15 * @author Andreas Gohr <andi@splitbrain.org>
16 */
17class GitToolCLI extends DokuCLI {
18
19    /**
20     * Register options and arguments on the given $options object
21     *
22     * @param DokuCLI_Options $options
23     * @return void
24     */
25    protected function setup(DokuCLI_Options $options) {
26        $options->setHelp(
27                "Manage git repositories for DokuWiki and its plugins and templates.\n\n" .
28                "EXAMPLE\n\n" .
29                "$> ./bin/gittool.php clone gallery template:ach\n" .
30                "$> ./bin/gittool.php repos\n" .
31                "$> ./bin/gittool.php origin -v\n"
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 DokuCLI_Options $options
82     * @return void
83     */
84    protected function main(DokuCLI_Options $options) {
85        $command = $options->getCmd();
86        if(!$command) $command = array_shift($options->args);
87
88        switch($command) {
89            case '':
90                echo $options->help('foo');
91                break;
92            case 'clone':
93                $this->cmd_clone($options->args);
94                break;
95            case 'install':
96                $this->cmd_install($options->args);
97                break;
98            case 'repo':
99            case 'repos':
100                $this->cmd_repos();
101                break;
102            default:
103                $this->cmd_git($command, $options->args);
104        }
105    }
106
107
108    /**
109     * Tries to install the given extensions using git clone
110     *
111     * @param       $extensions
112     */
113    public function cmd_clone($extensions) {
114        $errors    = array();
115        $succeeded = array();
116
117        foreach($extensions as $ext) {
118            $repo = $this->getSourceRepo($ext);
119
120            if(!$repo) {
121                $this->error("could not find a repository for $ext");
122                $errors[] = $ext;
123            } else {
124                if($this->cloneExtension($ext, $repo)) {
125                    $succeeded[] = $ext;
126                } else {
127                    $errors[] = $ext;
128                }
129            }
130        }
131
132        echo "\n";
133        if($succeeded) $this->success('successfully cloned the following extensions: ' . join(', ', $succeeded));
134        if($errors) $this->error('failed to clone the following extensions: ' . join(', ', $errors));
135    }
136
137    /**
138     * Tries to install the given extensions using git clone with fallback to install
139     *
140     * @param       $extensions
141     */
142    public function cmd_install($extensions) {
143        $errors    = array();
144        $succeeded = array();
145
146        foreach($extensions as $ext) {
147            $repo = $this->getSourceRepo($ext);
148
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;
155                }
156            } else {
157                if($this->cloneExtension($ext, $repo)) {
158                    $succeeded[] = $ext;
159                } else {
160                    $errors[] = $ext;
161                }
162            }
163        }
164
165        echo "\n";
166        if($succeeded) $this->success('successfully installed the following extensions: ' . join(', ', $succeeded));
167        if($errors) $this->error('failed to install the following extensions: ' . join(', ', $errors));
168    }
169
170    /**
171     * Executes the given git command in every repository
172     *
173     * @param $cmd
174     * @param $arg
175     */
176    public function cmd_git($cmd, $arg) {
177        $repos = $this->findRepos();
178
179        $shell = array_merge(array('git', $cmd), $arg);
180        $shell = array_map('escapeshellarg', $shell);
181        $shell = join(' ', $shell);
182
183        foreach($repos as $repo) {
184            if(!@chdir($repo)) {
185                $this->error("Could not change into $repo");
186                continue;
187            }
188
189            echo "\n";
190            $this->info("executing $shell in $repo");
191            $ret = 0;
192            system($shell, $ret);
193
194            if($ret == 0) {
195                $this->success("git succeeded in $repo");
196            } else {
197                $this->error("git failed in $repo");
198            }
199        }
200    }
201
202    /**
203     * Simply lists the repositories
204     */
205    public function cmd_repos() {
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
217     */
218    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");
222        $plugin->setExtension($ext);
223
224        $url = $plugin->getDownloadURL();
225        if(!$url) {
226            $this->error("no download URL for $ext");
227            return false;
228        }
229
230        $ok = false;
231        try {
232            $this->info("installing $ext via download from $url");
233            $ok = $plugin->installFromURL($url);
234        } catch(Exception $e) {
235            $this->error($e->getMessage());
236        }
237
238        if($ok) {
239            $this->success("installed $ext via download");
240            return true;
241        } else {
242            $this->success("failed to install $ext via download");
243            return false;
244        }
245    }
246
247    /**
248     * Clones the extension from the given repository
249     *
250     * @param string $ext
251     * @param string $repo
252     * @return bool
253     */
254    private function cloneExtension($ext, $repo) {
255        if(substr($ext, 0, 9) == 'template:') {
256            $target = fullpath(tpl_incdir() . '../' . substr($ext, 9));
257        } else {
258            $target = DOKU_PLUGIN . $ext;
259        }
260
261        $this->info("cloning $ext from $repo to $target");
262        $ret = 0;
263        system("git clone $repo $target", $ret);
264        if($ret === 0) {
265            $this->success("cloning of $ext succeeded");
266            return true;
267        } else {
268            $this->error("cloning of $ext failed");
269            return false;
270        }
271    }
272
273    /**
274     * Returns all git repositories in this DokuWiki install
275     *
276     * Looks in root, template and plugin directories only.
277     *
278     * @return array
279     */
280    private function findRepos() {
281        $this->info('Looking for .git directories');
282        $data = array_merge(
283            glob(DOKU_INC . '.git', GLOB_ONLYDIR),
284            glob(DOKU_PLUGIN . '*/.git', GLOB_ONLYDIR),
285            glob(fullpath(tpl_incdir() . '../') . '/*/.git', GLOB_ONLYDIR)
286        );
287
288        if(!$data) {
289            $this->error('Found no .git directories');
290        } else {
291            $this->success('Found ' . count($data) . ' .git directories');
292        }
293        $data = array_map('fullpath', array_map('dirname', $data));
294        return $data;
295    }
296
297    /**
298     * Returns the repository for the given extension
299     *
300     * @param $extension
301     * @return bool|string
302     */
303    private function getSourceRepo($extension) {
304        /** @var helper_plugin_extension_extension $ext */
305        $ext = plugin_load('helper', 'extension_extension');
306        if(!$ext) die("extension plugin not available, can't continue");
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
340$GitToolCLI = new GitToolCLI();
341$GitToolCLI->run();