init-repository: avoid failure when combining `--force' and mirrors
[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     qt5-staging
192     qt3support-staging
193     qtactiveqt-staging
194     qtbase-earth-staging
195     qtbase-staging
196     qtdeclarative-staging
197     qtdoc-staging
198     qtmultimedia-staging
199     qtphonon-staging
200     qtqa-staging
201     qtscript-staging
202     qtsvg-staging
203     qttools-staging
204     qttranslations-staging
205     qtwebkit-examples-and-demos-staging
206     qtxmlpatterns-staging
207 );
208
209 my $BNE_MIRROR_URL_BASE
210     = 'git://bq-git.apac.nokia.com/qtsoftware/qt/';
211
212 my $BNE_MIRROR_WEBKIT_URL
213     = 'git://bq-git.apac.nokia.com/qtsoftware/research/gitorious-org-webkit-qtwebkit-mirror.git';
214
215 sub new
216 {
217     my ($class, @arguments) = @_;
218
219     my $self = {};
220     bless $self, $class;
221     $self->parse_arguments(@arguments);
222
223     return $self;
224 }
225
226 # Like `system', but possibly log the command, and die on non-zero exit code
227 sub exe
228 {
229     my ($self, @cmd) = @_;
230
231     if (!$self->{quiet}) {
232         print "+ @cmd\n";
233     }
234
235     if (system(@cmd) != 0) {
236         confess "@cmd exited with status $CHILD_ERROR";
237     }
238
239     return;
240 }
241
242 sub parse_arguments
243 {
244     my ($self, @args) = @_;
245
246     %{$self} = (%{$self},
247         'alternates'          => "",
248         'detach-alternates'   => 0 ,
249         'force'               => 0 ,
250         'ignore-submodules'   => 0 ,
251         'mirror-url'          => "",
252         'mirror-webkit-url'   => "",
253         'nokia-developer'     => 0 ,
254         'protocol'            => "",
255         'update'              => 1 ,
256         'webkit'              => 1 ,
257     );
258
259     GetOptionsFromArray(\@args,
260         'alternates=s'      =>  \$self->{qw{ alternates        }},
261         'copy-objects'      =>  \$self->{qw{ detach-alternates }},
262         'force'             =>  \$self->{qw{ force             }},
263         'ignore-submodules' =>  \$self->{qw{ ignore_submodules }},
264         'mirror-webkit=s'   =>  \$self->{qw{ mirror-webkit-url }},
265         'mirror=s'          =>  \$self->{qw{ mirror-url        }},
266         'nokia-developer'   =>  \$self->{qw{ nokia-developer   }},
267         'quiet'             =>  \$self->{qw{ quiet             }},
268         'update!'           =>  \$self->{qw{ update            }},
269         'webkit!'           =>  \$self->{qw{ webkit            }},
270
271         'help|?'            =>  sub { pod2usage(1);                   },
272         'http'              =>  sub { $self->{protocol} = 'http'; },
273         'ssh|ssh-protocol'  =>  sub { $self->{protocol} = 'ssh';  },
274
275         'brisbane|brisbane-nokia-developer' => sub {
276             $self->{'nokia-developer'}   = 1;
277             $self->{'protocol'}          = 'internal';
278             $self->{'mirror-url'}        = $BNE_MIRROR_URL_BASE;
279             $self->{'mirror-webkit-url'} = $BNE_MIRROR_WEBKIT_URL;
280         },
281
282         'nokia-developer' => sub {
283             $self->{'nokia-developer'}   = 1;
284             $self->{'protocol'}          = 'internal';
285         },
286     ) || pod2usage(2);
287
288     if ($self->{'nokia-developer'} && $self->{'protocol'} eq 'http') {
289         print "*** Ignoring use of HTTP protocol, as it's only usable with external server\n";
290         $self->{'protocol'} = '';
291     }
292
293     # Replace any double trailing slashes from end of mirror
294     $self->{'mirror-url'} =~ s{//+$}{/};
295
296     return;
297 }
298
299 sub check_if_already_initialized
300 {
301     my ($self) = @_;
302
303     # We consider the repo as `initialized' if submodule.qtbase.url is set
304     if (qx(git config --get submodule.qtbase.url)) {
305         if ($self->{force}) {
306             my @configresult = qx(git config -l);
307             foreach (@configresult) {
308                 # Example line: submodule.qtqa.url=git://gitorious.org/qt/qtqa.git
309                 if (/(submodule\.[^.=]+)\.url=.*/) {
310                     $self->exe('git', 'config', '--remove-section', $1);
311                 }
312             }
313         }
314         else {
315             exit 0 if ($self->{quiet});
316             print "Will not reinitialize already initialized repository (use -f to force)!\n";
317             exit 1;
318         }
319     }
320
321     return;
322 }
323
324 sub git_submodule_init
325 {
326     my ($self) = @_;
327
328     my @init_args;
329     if ($self->{quiet}) {
330         push @init_args, '--quiet';
331     }
332     $self->exe('git', 'submodule', 'init', @init_args);
333
334     return;
335 }
336
337 sub git_disable_webkit_submodule
338 {
339     my ($self) = @_;
340
341     $self->exe('git', 'config', '--remove', 'submodule.qtwebkit');
342     $self->exe('git', 'config', '--remove', 'submodule.qtwebkit-examples-and-demos');
343
344     return;
345 }
346
347 sub git_set_submodule_config
348 {
349     my ($self) = @_;
350
351     my @configresult          = qx(git config -l);
352     my $protocol              = $self->{protocol};
353     my $url_base_for_protocol = $PROTOCOLS{$protocol};
354
355     GITCONFIG:
356     foreach my $line (@configresult) {
357         # Example line: submodule.qtqa.url=git://gitorious.org/qt/qtqa.git
358         next GITCONFIG if ($line !~ /(submodule\.[^.=]+\.url)=(.*)/);
359
360         my $key   = $1;
361         my $value = $2;
362
363         if ($protocol) {
364             # WebKit is special, and has only external link.
365             if ($key ne 'submodule.qtwebkit.url') {
366                 # qt-labs projects are still hosted under qt internally.
367                 if ($protocol ne 'http') {
368                     $value =~ s,^git://gitorious\.org/qt-labs/,${url_base_for_protocol}qt/,;
369                 }
370                 $value =~ s,^git://gitorious\.org/,$url_base_for_protocol,;
371             }
372         }
373
374         $self->exe('git', 'config', $key, $value);
375
376         if ($self->{'ignore-submodules'}) {
377             $key =~ s,\.url,.ignore,;
378             $self->exe('git', 'config', $key, 'all');
379         }
380     }
381
382     return;
383 }
384
385 sub git_clone_all_submodules
386 {
387     my ($self) = @_;
388
389     # manually clone each repo here, so we can easily use reference repos, mirrors and
390     # add all staging repos
391     my @configresult = qx(git config -l);
392     foreach my $line (@configresult) {
393         if ($line =~ /submodule\.([^.=]+)\.url=(.*)/) {
394             $self->git_clone_one_submodule($1, $2);
395         }
396     }
397
398     $self->exe('git', 'submodule', 'update');
399
400     return;
401 }
402
403 sub git_add_staging_remote
404 {
405     my ($self, $repo_basename) = @_;
406
407     my $protocol              = $self->{protocol};
408     my $url_base_for_protocol = $PROTOCOLS{$protocol};
409
410     my %current_remotes;
411     for my $line (qx(git remote show)) {
412         chomp $line;
413         $current_remotes{$line} = 1;
414     }
415
416     # We assume that any staging starting with `$repo_basename-' relates to this
417     # repo.  For example, for the `qtbase' module, `qtbase-staging'
418     # and `qtbase-earth-staging' are considered as related staging repos.
419     my @staging = grep { /^\Q$repo_basename\E-/; } keys %STAGING_REPOS;
420
421     STAGING:
422     foreach my $staging_repo (@staging) {
423         # nothing to do if remote already exists
424         next STAGING if ($current_remotes{$staging_repo});
425
426         my $staging_repo_url = $STAGING_REPOS{$staging_repo};
427         if ($protocol) {
428             if ($protocol ne 'http') {
429                 $staging_repo_url =~ s,^git://gitorious\.org/qt-labs/,${url_base_for_protocol}qt/,;
430             }
431             $staging_repo_url =~ s,^git://gitorious\.org/,$url_base_for_protocol,;
432         }
433         $self->exe('git', 'remote', 'add', $staging_repo, $staging_repo_url);
434     }
435
436     return;
437 }
438
439 sub git_clone_one_submodule
440 {
441     my ($self, $submodule, $url) = @_;
442
443     my $alternates            = $self->{ 'alternates'        };
444     my $mirror_url            = $self->{ 'mirror-url'        };
445     my $mirror_webkit_url     = $self->{ 'mirror-webkit-url' };
446
447     # `--reference FOO' args for the clone, if any.
448     my @reference_args;
449
450     if ($alternates) {
451         # alternates is a qt5 repo, so the submodule will be under that.
452         if (-d "$alternates/$submodule") {
453             @reference_args = ('--reference', "$alternates/$submodule");
454         }
455         else {
456             print " *** $alternates/$submodule not found, ignoring alternate for this submodule\n";
457         }
458     }
459
460     my $mirror;
461     if ($mirror_url && ($submodule ne 'qtwebkit')) {
462         $mirror = $mirror_url.$submodule;
463         $mirror .= ".git" unless (-d $mirror); # Support local disk mirror
464     }
465     elsif ($mirror_webkit_url && ($submodule eq 'qtwebkit')) {
466         $mirror = $mirror_webkit_url;
467     }
468
469     my $do_clone = (! -d "$submodule/.git");
470     if ($do_clone) {
471         $self->exe('git', 'clone', @reference_args, ($mirror ? $mirror : $url), $submodule);
472     }
473
474     chdir($submodule) or confess "chdir $submodule: $OS_ERROR";
475
476     if (!$do_clone) {
477         $self->exe('git', 'fetch', ($mirror ? $mirror : $url));
478     }
479
480     if ($mirror) {
481         $self->exe('git', 'config', 'remote.origin.url', $url);
482
483         # In `force' mode, remove the mirror if it already exists,
484         # since we may be reinitializing the module.
485         if ($self->{force}) {
486             eval { $self->exe('git', 'remote', 'rm', 'mirror'); }; # failure is OK
487         }
488
489         $self->exe('git', 'remote', 'add', 'mirror', $mirror);
490     }
491
492     $self->git_add_staging_remote($submodule);
493
494     if ($self->{'detach-alternates'}) {
495         $self->exe('git', 'repack', '-a');
496
497         my $alternates_path = '.git/objects/info/alternates';
498         unlink($alternates_path) || confess "unlink $alternates_path: $OS_ERROR";
499     }
500
501     chdir("..") or confess "cd ..: $OS_ERROR";
502
503     return;
504 }
505
506 sub run
507 {
508     my ($self) = @_;
509
510     $self->check_if_already_initialized;
511     $self->git_submodule_init;
512
513     if (!$self->{webkit}) {
514         $self->git_disable_webkit_submodule;
515     }
516
517     $self->git_set_submodule_config;
518
519     if ($self->{update}) {
520         $self->git_clone_all_submodules;
521     }
522
523     $self->git_add_staging_remote('qt5');
524
525     return;
526 }
527
528 #==============================================================================
529
530 Qt::InitRepository->new(@ARGV)->run if (!caller);
531 1;