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