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