Improve maintainability of init-repository script.
[mirror/qt/qt5.git] / init-repository
1 #!/usr/bin/env perl
2 #############################################################################
3 ##
4 ## Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
5 ## All rights reserved.
6 ## Contact: Nokia Corporation (qt-info@nokia.com)
7 ##
8 ## This file is part of the utilities of the Qt Toolkit.
9 ##
10 ## $QT_BEGIN_LICENSE:LGPL$
11 ## No Commercial Usage
12 ## This file contains pre-release code and may not be distributed.
13 ## You may use this file in accordance with the terms and conditions
14 ## contained in the Technology Preview License Agreement accompanying
15 ## this package.
16 ##
17 ## GNU Lesser General Public License Usage
18 ## Alternatively, this file may be used under the terms of the GNU Lesser
19 ## General Public License version 2.1 as published by the Free Software
20 ## Foundation and appearing in the file LICENSE.LGPL included in the
21 ## packaging of this file.  Please review the following information to
22 ## ensure the GNU Lesser General Public License version 2.1 requirements
23 ## will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ##
25 ## In addition, as a special exception, Nokia gives you certain additional
26 ## rights.  These rights are described in the Nokia Qt LGPL Exception
27 ## version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ##
29 ## If you have questions regarding the use of this file, please contact
30 ## Nokia at qt-info@nokia.com.
31 ##
32 ##
33 ##
34 ##
35 ##
36 ##
37 ##
38 ##
39 ## $QT_END_LICENSE$
40 ##
41 #############################################################################
42
43 use strict;
44 use warnings;
45
46 package Qt::InitRepository;
47
48
49 =head1 NAME
50
51 init-repository - initialize the Qt5 repository and all submodules
52
53 =head1 SYNOPSIS
54
55   ./init-repository [options]
56
57 This script may be run after an initial `git clone' of Qt5 in order to check
58 out all submodules.
59
60
61 =head2 Global options:
62
63 =over
64
65 =item --force, -f
66
67 Force initialization (even if the submodules are already checked out).
68
69
70 =item --quiet, -q
71
72 Be quiet.  Will exit cleanly if the repository is already initialized.
73
74 =back
75
76
77 =head2 Module options:
78
79 =over
80
81 =item --no-webkit
82
83 Skip webkit and webkit examples submodules.
84 It may be desirable to skip these modules due to the large size of the webkit
85 git repository.
86
87
88 =item --no-update
89
90 Skip the `git submodule update' command.
91
92
93 =item --ignore-submodules
94
95 Set git config to ignore submodules by default when doing operations on the
96 qt5 repo, such as `pull', `fetch', `diff' etc.
97
98 After using this option, pass `--ignore-submodules=none' to git to override
99 it as needed.
100
101 =back
102
103
104 =head2 Repository options:
105
106 =over
107
108 =item --nokia-developer
109
110 Switch to internal Nokia URLs.
111
112
113 =item --brisbane
114
115 Switch to internal Nokia URLs and make use of the Brisbane git mirrors.
116 (Implies `--mirror' and `--mirror-webkit').
117
118
119 =item --ssh
120
121 Use the SSH protocol for git operations.  This may be useful if the git
122 protocol is blocked by a firewall. Note that this requires a user account
123 with an uploaded SSH key on all servers used.  (Implies `--nokia-developer').
124
125
126 =item --http
127
128 Use the HTTP protocol for git operations.  This may be useful if the git
129 protocol is blocked by a firewall.  Note that this only works with the
130 external Gitorious server.
131
132
133 =item --alternates <path to other Qt5 repo>
134
135 Adds alternates for each submodule to another full qt5 checkout. This makes
136 this qt5 checkout very small, as it will use the object store of the
137 alternates before unique objects are stored in its own object store.
138
139 This option has no effect when using `--no-update'.
140
141 B<NOTE:> This will make this repo dependent on the alternate, which is
142 potentially dangerous!  The dependency can be broken by also using
143 the `--copy-objects' option, or by running C<git repack -a> in each
144 submodule, where required.  Please read the note about the `--shared' option
145 in the documentation of `git clone' for more information.
146
147
148 =item --copy-objects
149
150 When `--alternates' is used, automatically do a C<git repack -a> in each
151 submodule after cloning, to ensure that the repositories are independent
152 from the source used as a reference for cloning.
153
154 Note that this negates the disk usage benefits gained from the use of
155 `--alternates'.
156
157
158 =item --mirror <url-base>
159
160 Uses <url-base> as the base URL for submodule git mirrors.
161
162 For example:
163
164   --mirror user@machine:/foo/bar
165
166 ...will use the following as a mirror for qtbase:
167
168   user@machine:/foo/bar/qtbase.git
169
170
171 =item --mirror-webkit <url>
172
173 Uses <url> as the URL for the webkit git mirror.
174
175 =back
176
177 =cut
178
179 use Carp         qw( confess             );
180 use English      qw( -no_match_vars      );
181 use Getopt::Long qw( GetOptionsFromArray );
182 use Pod::Usage   qw( pod2usage           );
183
184 my %PROTOCOLS = (
185     'internal'  => 'git://scm.dev.nokia.troll.no/' ,
186     'ssh'       => 'git@scm.dev.nokia.troll.no:'   ,
187     'http'      => 'http://git.gitorious.org/'     ,
188 );
189
190 my %STAGING_REPOS = map { $_ => "git://gitorious.org/qt/$_.git" } qw(
191     qt3support-staging
192     qtactiveqt-staging
193     qtbase-earth-staging
194     qtbase-staging
195     qtdeclarative-staging
196     qtdoc-staging
197     qtmultimedia-staging
198     qtphonon-staging
199     qtqa-staging
200     qtscript-staging
201     qtsvg-staging
202     qttools-staging
203     qttranslations-staging
204     qtwebkit-examples-and-demos-staging
205     qtxmlpatterns-staging
206 );
207
208 my $BNE_MIRROR_URL_BASE
209     = 'git://bq-git.apac.nokia.com/qtsoftware/qt/';
210
211 my $BNE_MIRROR_WEBKIT_URL
212     = 'git://bq-git.apac.nokia.com/qtsoftware/research/gitorious-org-webkit-qtwebkit-mirror.git';
213
214 sub new
215 {
216     my ($class, @arguments) = @_;
217
218     my $self = {};
219     bless $self, $class;
220     $self->parse_arguments(@arguments);
221
222     return $self;
223 }
224
225 # Like `system', but possibly log the command, and die on non-zero exit code
226 sub exe
227 {
228     my ($self, @cmd) = @_;
229
230     if (!$self->{quiet}) {
231         print "+ @cmd\n";
232     }
233
234     if (system(@cmd) != 0) {
235         confess "@cmd exited with status $CHILD_ERROR";
236     }
237
238     return;
239 }
240
241 sub parse_arguments
242 {
243     my ($self, @args) = @_;
244
245     %{$self} = (%{$self},
246         'alternates'          => "",
247         'detach-alternates'   => 0 ,
248         'force'               => 0 ,
249         'ignore-submodules'   => 0 ,
250         'mirror-url'          => "",
251         'mirror-webkit-url'   => "",
252         'nokia-developer'     => 0 ,
253         'protocol'            => "",
254         'update'              => 1 ,
255         'webkit'              => 1 ,
256     );
257
258     GetOptionsFromArray(\@args,
259         'alternates=s'      =>  \$self->{qw{ alternates        }},
260         'copy-objects'      =>  \$self->{qw{ detach-alternates }},
261         'force'             =>  \$self->{qw{ force             }},
262         'ignore-submodules' =>  \$self->{qw{ ignore_submodules }},
263         'mirror-webkit=s'   =>  \$self->{qw{ mirror-webkit-url }},
264         'mirror=s'          =>  \$self->{qw{ mirror-url        }},
265         'nokia-developer'   =>  \$self->{qw{ nokia-developer   }},
266         'quiet'             =>  \$self->{qw{ quiet             }},
267         'update!'           =>  \$self->{qw{ update            }},
268         'webkit!'           =>  \$self->{qw{ webkit            }},
269
270         'help|?'            =>  sub { pod2usage(1);                   },
271         'http'              =>  sub { $self->{protocol} = 'http'; },
272         'ssh|ssh-protocol'  =>  sub { $self->{protocol} = 'ssh';  },
273
274         'brisbane|brisbane-nokia-developer' => sub {
275             $self->{'nokia-developer'}   = 1;
276             $self->{'protocol'}          = 'internal';
277             $self->{'mirror-url'}        = $BNE_MIRROR_URL_BASE;
278             $self->{'mirror-webkit-url'} = $BNE_MIRROR_WEBKIT_URL;
279         },
280
281         'nokia-developer' => sub {
282             $self->{'nokia-developer'}   = 1;
283             $self->{'protocol'}          = 'internal';
284         },
285     ) || pod2usage(2);
286
287     if ($self->{'nokia-developer'} && $self->{'protocol'} eq 'http') {
288         print "*** Ignoring use of HTTP protocol, as it's only usable with external server\n";
289         $self->{'protocol'} = '';
290     }
291
292     # Replace any double trailing slashes from end of mirror
293     $self->{'mirror-url'} =~ s{//+$}{/};
294
295     return;
296 }
297
298 sub check_if_already_initialized
299 {
300     my ($self) = @_;
301
302     # We consider the repo as `initialized' if submodule.qtbase.url is set
303     if (qx(git config --get submodule.qtbase.url)) {
304         if ($self->{force}) {
305             my @configresult = qx(git config -l);
306             foreach (@configresult) {
307                 # Example line: submodule.qtqa.url=git://gitorious.org/qt/qtqa.git
308                 if (/(submodule\.[^.=]+)\.url=.*/) {
309                     $self->exe('git', 'config', '--remove-section', $1);
310                 }
311             }
312         }
313         else {
314             exit 0 if ($self->{quiet});
315             print "Will not reinitialize already initialized repository (use -f to force)!\n";
316             exit 1;
317         }
318     }
319
320     return;
321 }
322
323 sub git_submodule_init
324 {
325     my ($self) = @_;
326
327     my @init_args;
328     if ($self->{quiet}) {
329         push @init_args, '--quiet';
330     }
331     $self->exe('git', 'submodule', 'init', @init_args);
332
333     return;
334 }
335
336 sub git_disable_webkit_submodule
337 {
338     my ($self) = @_;
339
340     $self->exe('git', 'config', '--remove', 'submodule.qtwebkit');
341     $self->exe('git', 'config', '--remove', 'submodule.qtwebkit-examples-and-demos');
342
343     return;
344 }
345
346 sub git_set_submodule_config
347 {
348     my ($self) = @_;
349
350     my @configresult          = qx(git config -l);
351     my $protocol              = $self->{protocol};
352     my $url_base_for_protocol = $PROTOCOLS{$protocol};
353
354     GITCONFIG:
355     foreach my $line (@configresult) {
356         # Example line: submodule.qtqa.url=git://gitorious.org/qt/qtqa.git
357         next GITCONFIG if ($line !~ /(submodule\.[^.=]+\.url)=(.*)/);
358
359         my $key   = $1;
360         my $value = $2;
361
362         if ($protocol) {
363             # WebKit is special, and has only external link.
364             if ($key ne 'submodule.qtwebkit.url') {
365                 # qt-labs projects are still hosted under qt internally.
366                 if ($protocol ne 'http') {
367                     $value =~ s,^git://gitorious\.org/qt-labs/,${url_base_for_protocol}qt/,;
368                 }
369                 $value =~ s,^git://gitorious\.org/,$url_base_for_protocol,;
370             }
371         }
372
373         $self->exe('git', 'config', $key, $value);
374
375         if ($self->{'ignore-submodules'}) {
376             $key =~ s,\.url,.ignore,;
377             $self->exe('git', 'config', $key, 'all');
378         }
379     }
380
381     return;
382 }
383
384 sub git_clone_all_submodules
385 {
386     my ($self) = @_;
387
388     # manually clone each repo here, so we can easily use reference repos, mirrors and
389     # add all staging repos
390     my @configresult = qx(git config -l);
391     foreach my $line (@configresult) {
392         if ($line =~ /submodule\.([^.=]+)\.url=(.*)/) {
393             $self->git_clone_one_submodule($1, $2);
394         }
395     }
396
397     $self->exe('git', 'submodule', 'update');
398
399     return;
400 }
401
402 sub git_clone_one_submodule
403 {
404     my ($self, $submodule, $url) = @_;
405
406     my $alternates            = $self->{ 'alternates'        };
407     my $mirror_url            = $self->{ 'mirror-url'        };
408     my $mirror_webkit_url     = $self->{ 'mirror-webkit-url' };
409     my $protocol              = $self->{protocol};
410     my $url_base_for_protocol = $PROTOCOLS{$protocol};
411
412     # `--reference FOO' args for the clone, if any.
413     my @reference_args;
414
415     if ($alternates) {
416         # alternates is a qt5 repo, so the submodule will be under that.
417         if (-d "$alternates/$submodule") {
418             @reference_args = ('--reference', "$alternates/$submodule");
419         }
420         else {
421             print " *** $alternates/$submodule not found, ignoring alternate for this submodule\n";
422         }
423     }
424
425     my $mirror;
426     if ($mirror_url && ($submodule ne 'qtwebkit')) {
427         $mirror = $mirror_url.$submodule;
428         $mirror .= ".git" unless (-d $mirror); # Support local disk mirror
429     }
430     elsif ($mirror_webkit_url && ($submodule eq 'qtwebkit')) {
431         $mirror = $mirror_webkit_url;
432     }
433
434     my $do_clone = (! -d "$submodule/.git");
435     if ($do_clone) {
436         $self->exe('git', 'clone', @reference_args, ($mirror ? $mirror : $url), $submodule);
437     }
438
439     chdir($submodule) or confess "chdir $submodule: $OS_ERROR";
440
441     if (!$do_clone) {
442         $self->exe('git', 'fetch', ($mirror ? $mirror : $url));
443     }
444
445     if ($mirror) {
446         $self->exe('git', 'config', 'remote.origin.url', $url);
447         $self->exe('git', 'remote', 'add', 'mirror', $mirror);
448     }
449
450     my %current_remotes;
451     for my $line (qx(git remote show)) {
452         chomp $line;
453         $current_remotes{$line} = 1;
454     }
455
456     # We assume that any staging starting with `$submodule-' relates to this
457     # submodule.  For example, for the `qtbase' module, `qtbase-staging'
458     # and `qtbase-earth-staging' are considered as related staging repos.
459     my @staging = grep { /^\Q$submodule\E-/; } keys %STAGING_REPOS;
460
461     STAGING:
462     foreach my $staging_repo (@staging) {
463         # nothing to do if remote already exists
464         next STAGING if ($current_remotes{$staging_repo});
465
466         my $staging_repo_url = $STAGING_REPOS{$staging_repo};
467         if ($protocol) {
468             if ($protocol ne 'http') {
469                 $staging_repo_url =~ s,^git://gitorious\.org/qt-labs/,${url_base_for_protocol}qt/,;
470             }
471             $staging_repo_url =~ s,^git://gitorious\.org/,$url_base_for_protocol,;
472         }
473         $self->exe('git', 'remote', 'add', $staging_repo, $staging_repo_url);
474     }
475
476     if ($self->{'detach-alternates'}) {
477         $self->exe('git', 'repack', '-a');
478
479         my $alternates_path = '.git/objects/info/alternates';
480         unlink($alternates_path) || confess "unlink $alternates_path: $OS_ERROR";
481     }
482
483     chdir("..") or confess "cd ..: $OS_ERROR";
484
485     return;
486 }
487
488 sub run
489 {
490     my ($self) = @_;
491
492     $self->check_if_already_initialized;
493     $self->git_submodule_init;
494
495     if (!$self->{webkit}) {
496         $self->git_disable_webkit_submodule;
497     }
498
499     $self->git_set_submodule_config;
500
501     if ($self->{update}) {
502         $self->git_clone_all_submodules;
503     }
504
505     return;
506 }
507
508 #==============================================================================
509
510 Qt::InitRepository->new(@ARGV)->run if (!caller);
511 1;