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