Updated submodules.
[mirror/qt/qt5.git] / init-repository
1 #!/usr/bin/env perl
2 #############################################################################
3 ##
4 ## Copyright (C) 2015 The Qt Company Ltd.
5 ## Contact: http://www.qt.io/licensing/
6 ##
7 ## This file is part of the utilities of the Qt Toolkit.
8 ##
9 ## $QT_BEGIN_LICENSE:LGPL21$
10 ## Commercial License Usage
11 ## Licensees holding valid commercial Qt licenses may use this file in
12 ## accordance with the commercial license agreement provided with the
13 ## Software or, alternatively, in accordance with the terms contained in
14 ## a written agreement between you and The Qt Company. For licensing terms
15 ## and conditions see http://www.qt.io/terms-conditions. For further
16 ## information use the contact form at http://www.qt.io/contact-us.
17 ##
18 ## GNU Lesser General Public License Usage
19 ## Alternatively, this file may be used under the terms of the GNU Lesser
20 ## General Public License version 2.1 or version 3 as published by the Free
21 ## Software Foundation and appearing in the file LICENSE.LGPLv21 and
22 ## LICENSE.LGPLv3 included in the packaging of this file. Please review the
23 ## following information to ensure the GNU Lesser General Public License
24 ## requirements will be met: https://www.gnu.org/licenses/lgpl.html and
25 ## http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
26 ##
27 ## As a special exception, The Qt Company gives you certain additional
28 ## rights. These rights are described in The Qt Company LGPL Exception
29 ## version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
30 ##
31 ## $QT_END_LICENSE$
32 ##
33 #############################################################################
34
35 use v5.8;
36 use strict;
37 use warnings;
38
39 package Qt::InitRepository;
40
41
42 =head1 NAME
43
44 init-repository - initialize the Qt5 repository and all submodules
45
46 =head1 SYNOPSIS
47
48   ./init-repository [options]
49
50 This script may be run after an initial `git clone' of Qt5 in order to check
51 out all submodules.
52
53
54 =head1 OPTIONS
55
56 B<Global options:>
57
58 =over
59
60 =item --force, -f
61
62 Force initialization (even if the submodules are already checked out).
63
64
65 =item --force-hooks
66
67 Force initialization of hooks (even if there are already hooks in checked out
68 submodules).
69
70
71 =item --quiet, -q
72
73 Be quiet.  Will exit cleanly if the repository is already initialized.
74
75 =back
76
77
78 B<Module options:>
79
80 =over
81
82 =item --no-webkit
83
84 Skip webkit and webkit examples submodules.
85 It may be desirable to skip these modules due to the large size of the webkit
86 git repository.
87
88 =item --module-subset=<module1>,<module2>...
89
90 Only initialize the specified subset of modules given as the argument. Specified
91 modules must already exist in .gitmodules.
92 The string "all" results in cloning all known modules. The default is the set of
93 maintained modules.
94 Module names may be prefixed with a dash to exclude them from a bigger set.
95
96 =item --no-update
97
98 Skip the `git submodule update' command.
99
100 =item --branch
101
102 Instead of checking out specific SHA1s, check out the submodule branches that
103 correspond with the current supermodule commit.
104 By default, this option will cause local commits in the submodules to be rebased.
105 With --no-update, the branches will be checked out, but their heads will not move.
106
107 =item --ignore-submodules
108
109 Set git config to ignore submodules by default when doing operations on the
110 qt5 repo, such as `pull', `fetch', `diff' etc.
111
112 After using this option, pass `--ignore-submodules=none' to git to override
113 it as needed.
114
115 =back
116
117
118 B<Repository options:>
119
120 =over
121
122 =item --berlin
123
124 Switch to internal URLs and make use of the Berlin git mirrors.
125 (Implies `--mirror').
126
127 =item --oslo
128
129 Switch to internal URLs and make use of the Oslo git mirrors.
130 (Implies `--mirror').
131
132
133 =item --codereview-username <Gerrit/JIRA username>
134
135 Specify the user name for the (potentially) writable `gerrit' remote
136 for each module, for use with the Gerrit code review tool.
137
138 If this option is omitted, the gerrit remote is created without a username
139 and port number, and thus relies on a correct SSH configuration.
140
141
142 =item --alternates <path to other Qt5 repo>
143
144 Adds alternates for each submodule to another full qt5 checkout. This makes
145 this qt5 checkout very small, as it will use the object store of the
146 alternates before unique objects are stored in its own object store.
147
148 This option has no effect when using `--no-update'.
149
150 B<NOTE:> This will make this repo dependent on the alternate, which is
151 potentially dangerous!  The dependency can be broken by also using
152 the `--copy-objects' option, or by running C<git repack -a> in each
153 submodule, where required.  Please read the note about the `--shared' option
154 in the documentation of `git clone' for more information.
155
156
157 =item --copy-objects
158
159 When `--alternates' is used, automatically do a C<git repack -a> in each
160 submodule after cloning, to ensure that the repositories are independent
161 from the source used as a reference for cloning.
162
163 Note that this negates the disk usage benefits gained from the use of
164 `--alternates'.
165
166
167 =item --mirror <url-base>
168
169 Uses <url-base> as the base URL for submodule git mirrors.
170
171 For example:
172
173   --mirror user@machine:/foo/bar/
174
175 ...will use the following as a mirror for qtbase:
176
177   user@machine:/foo/bar/qt/qtbase.git
178
179 The mirror is permitted to contain a subset of the submodules; any
180 missing modules will fall back to the canonical URLs.
181
182 =back
183
184 =cut
185
186 use Carp         qw( confess             );
187 use English      qw( -no_match_vars      );
188 use Getopt::Long qw( GetOptionsFromArray );
189 use Pod::Usage   qw( pod2usage           );
190 use Cwd          qw( getcwd              );
191
192 my $GERRIT_SSH_BASE
193     = 'ssh://@USER@codereview.qt-project.org@PORT@/';
194
195 my $BER_MIRROR_URL_BASE
196     = 'git://hegel/';
197
198 my $OSLO_MIRROR_URL_BASE
199     = 'git://qilin/';
200
201 sub new
202 {
203     my ($class, @arguments) = @_;
204
205     my $self = {};
206     bless $self, $class;
207     $self->parse_arguments(@arguments);
208
209     return $self;
210 }
211
212 # Like `system', but possibly log the command, and die on non-zero exit code
213 sub exe
214 {
215     my ($self, @cmd) = @_;
216
217     if (!$self->{quiet}) {
218         print "+ @cmd\n";
219     }
220
221     if (system(@cmd) != 0) {
222         confess "@cmd exited with status $CHILD_ERROR";
223     }
224
225     return;
226 }
227
228 sub parse_arguments
229 {
230     my ($self, @args) = @_;
231
232     %{$self} = (%{$self},
233         'alternates'          => "",
234         'branch'              => 0,
235         'codereview-username' => "",
236         'detach-alternates'   => 0 ,
237         'force'               => 0 ,
238         'force-hooks'         => 0 ,
239         'ignore-submodules'   => 0 ,
240         'mirror-url'          => "",
241         'update'              => 1 ,
242         'webkit'              => 1 ,
243         'module-subset'       => "default",
244     );
245
246     GetOptionsFromArray(\@args,
247         'alternates=s'      =>  \$self->{qw{ alternates        }},
248         'branch'            =>  \$self->{qw{ branch            }},
249         'codereview-username=s' => \$self->{qw{ codereview-username }},
250         'copy-objects'      =>  \$self->{qw{ detach-alternates }},
251         'force|f'           =>  \$self->{qw{ force             }},
252         'force-hooks'       =>  \$self->{qw{ force-hooks       }},
253         'ignore-submodules' =>  \$self->{qw{ ignore-submodules }},
254         'mirror=s'          =>  \$self->{qw{ mirror-url        }},
255         'quiet'             =>  \$self->{qw{ quiet             }},
256         'update!'           =>  \$self->{qw{ update            }},
257         'webkit!'           =>  \$self->{qw{ webkit            }},
258         'module-subset=s'   =>  \$self->{qw{ module-subset     }},
259
260         'help|?'            =>  sub { pod2usage(1);               },
261
262         'berlin' => sub {
263             $self->{'mirror-url'}        = $BER_MIRROR_URL_BASE;
264         },
265         'oslo' => sub {
266             $self->{'mirror-url'}        = $OSLO_MIRROR_URL_BASE;
267         },
268     ) || pod2usage(2);
269
270     # Replace any double trailing slashes from end of mirror
271     $self->{'mirror-url'} =~ s{//+$}{/};
272
273     $self->{'module-subset'} = [ split(/,/, $self->{'module-subset'}) ];
274     if (!$self->{webkit}) {
275         push @{$self->{'module-subset'}}, "-qtwebkit", "-qtwebkit-examples";
276     }
277
278     return;
279 }
280
281 sub check_if_already_initialized
282 {
283     my ($self) = @_;
284
285     # We consider the repo as `initialized' if submodule.qtbase.url is set
286     if (qx(git config --get submodule.qtbase.url)) {
287         if (!$self->{force}) {
288             exit 0 if ($self->{quiet});
289             print "Will not reinitialize already initialized repository (use -f to force)!\n";
290             exit 1;
291         }
292     }
293
294     return;
295 }
296
297 sub git_submodule_init
298 {
299     my ($self, @init_args) = @_;
300
301     if ($self->{quiet}) {
302         unshift @init_args, '--quiet';
303     }
304     $self->exe('git', 'submodule', 'init', @init_args);
305
306     my $template = getcwd()."/.commit-template";
307     if (-e $template) {
308         $self->exe('git', 'config', 'commit.template', $template);
309     }
310
311     return;
312 }
313
314 sub git_clone_all_submodules
315 {
316     my ($self, $my_repo_base, $co_branch, @subset) = @_;
317
318     my %subdirs = ();
319     my %subbranches = ();
320     my %subbases = ();
321     my %subinits = ();
322     my @submodconfig = qx(git config -l -f .gitmodules);
323     foreach my $line (@submodconfig) {
324         # Example line: submodule.qtqa.url=../qtqa.git
325         next if ($line !~ /^submodule\.([^.=]+)\.([^.=]+)=(.*)$/);
326         if ($2 eq "path") {
327             $subdirs{$1} = $3;
328         } elsif ($2 eq "branch") {
329             $subbranches{$1} = $3;
330         } elsif ($2 eq "url") {
331             my ($mod, $base) = ($1, $3);
332             next if ($base !~ /^\.\.\//);
333             $base = $my_repo_base.'/'.$base;
334             while ($base =~ s,/(?!\.\./)[^/]+/\.\./,/,g) {}
335             $subbases{$mod} = $base;
336         } elsif ($2 eq "update") {
337             push @subset, '-'.$1 if ($3 eq 'ignore');
338         } elsif ($2 eq "initrepo") {
339             $subinits{$1} = ($3 eq "yes" or $3 eq "true");
340         }
341     }
342
343     my %include = ();
344     foreach my $mod (@subset) {
345         if ($mod eq "all") {
346             map { $include{$_} = 1; } keys %subbases;
347         } elsif ($mod eq "default") {
348             map { $include{$_} = 1; } grep { $subinits{$_} } keys %subbases;
349         } elsif ($mod =~ s/^-//) {
350             delete $include{$mod};
351         } else {
352             $include{$mod} = 1;
353         }
354     }
355
356     my @modules = sort keys %include;
357
358     $self->git_submodule_init(map { $subdirs{$_} } @modules);
359
360     # manually clone each repo here, so we can easily use reference repos, mirrors etc
361     my @configresult = qx(git config -l);
362     foreach my $line (@configresult) {
363         # Example line: submodule.qtqa.url=git://gitorious.org/qt/qtqa.git
364         next if ($line !~ /submodule\.([^.=]+)\.url=/);
365         my $module = $1;
366
367         if (!defined($include{$module})) {
368             $self->exe('git', 'config', '--remove-section', "submodule.$module");
369             next;
370         }
371
372         if ($self->{'ignore-submodules'}) {
373             $self->exe('git', 'config', "submodule.$module.ignore", 'all');
374         }
375     }
376
377     foreach my $module (@modules) {
378         $self->git_clone_one_submodule($subdirs{$module}, $subbases{$module}, $subbranches{$module});
379     }
380
381     if ($co_branch) {
382         foreach my $module (@modules) {
383             my $branch = $subbranches{$module};
384             die("No branch defined for submodule $module.\n") if (!defined($branch));
385             my $orig_cwd = getcwd();
386             chdir($module) or confess "chdir $module: $OS_ERROR";
387             my $br = qx(git rev-parse -q --verify $branch);
388             if (!$br) {
389                 $self->exe('git', 'checkout', '-b', $branch, "origin/$branch");
390             } else {
391                 $self->exe('git', 'checkout', $branch);
392             }
393             chdir("$orig_cwd") or confess "chdir $orig_cwd: $OS_ERROR";
394         }
395     }
396     if ($self->{update}) {
397         my @cmd = ('git', 'submodule', 'update', '--no-fetch');
398         push @cmd, '--remote', '--rebase' if ($co_branch);
399         $self->exe(@cmd);
400
401         foreach my $module (@modules) {
402             if (-f $module.'/.gitmodules') {
403                 my $orig_cwd = getcwd();
404                 chdir($module) or confess "chdir $module: $OS_ERROR";
405                 $self->git_clone_all_submodules($subbases{$module}, 0, "all");
406                 chdir("$orig_cwd") or confess "chdir $orig_cwd: $OS_ERROR";
407             }
408         }
409     }
410
411     return;
412 }
413
414 sub git_add_remotes
415 {
416     my ($self, $gerrit_repo_basename) = @_;
417
418     my $gerrit_repo_url = $GERRIT_SSH_BASE;
419     # If given a username, make a "verbose" remote.
420     # Otherwise, rely on proper SSH configuration.
421     if ($self->{'codereview-username'}) {
422         $gerrit_repo_url =~ s,\@USER\@,$self->{'codereview-username'}\@,;
423         $gerrit_repo_url =~ s,\@PORT\@,:29418,;
424     } else {
425         $gerrit_repo_url =~ s,\@[^\@]+\@,,g;
426     }
427
428     $gerrit_repo_url .= $gerrit_repo_basename;
429     $self->exe('git', 'config', 'remote.gerrit.url', $gerrit_repo_url);
430     $self->exe('git', 'config', 'remote.gerrit.fetch', '+refs/heads/*:refs/remotes/gerrit/*', '/heads/');
431 }
432
433 sub git_clone_one_submodule
434 {
435     my ($self, $submodule, $repo_basename, $branch) = @_;
436
437     my $alternates            = $self->{ 'alternates'        };
438     my $mirror_url            = $self->{ 'mirror-url'        };
439     my $protocol              = $self->{ 'protocol'          };
440
441     # `--reference FOO' args for the clone, if any.
442     my @reference_args;
443
444     if ($alternates) {
445         # alternates is a qt5 repo, so the submodule will be under that.
446         if (-e "$alternates/$submodule/.git") {
447             @reference_args = ('--reference', "$alternates/$submodule");
448         }
449         else {
450             print " *** $alternates/$submodule not found, ignoring alternate for this submodule\n";
451         }
452     }
453
454     my $url = $self->{'base-url'}.$repo_basename;
455     my $mirror;
456     if ($mirror_url) {
457         $mirror = $mirror_url.$repo_basename;
458     }
459
460     if ($mirror) {
461         # Only use the mirror if it can be reached.
462         eval { $self->exe('git', 'ls-remote', $mirror, 'test/if/mirror/exists') };
463         if ($@) {
464             warn "mirror [$mirror] is not accessible; $url will be used\n";
465             undef $mirror;
466         }
467     }
468
469     my $do_clone = (! -e "$submodule/.git");
470     if ($do_clone) {
471         push @reference_args, '--branch', $branch if ($branch);
472         $self->exe('git', 'clone', @reference_args,
473                    ($mirror ? $mirror : $url), $submodule);
474     }
475
476     my $orig_cwd = getcwd();
477     chdir($submodule) or confess "chdir $submodule: $OS_ERROR";
478
479     if ($mirror) {
480         # This is only for the user's convenience - we make no use of it.
481         $self->exe('git', 'config', 'remote.mirror.url', $mirror);
482         $self->exe('git', 'config', 'remote.mirror.fetch', '+refs/heads/*:refs/remotes/mirror/*');
483     }
484
485     if (!$do_clone && $self->{update}) {
486         # If we didn't clone, fetch from the right location. We always update
487         # the origin remote, so that submodule update --remote works.
488         $self->exe('git', 'config', 'remote.origin.url', ($mirror ? $mirror : $url));
489         $self->exe('git', 'fetch', 'origin');
490     }
491
492     if (!($do_clone || $self->{update}) || $mirror) {
493         # Leave the origin configured to the canonical URL. It's already correct
494         # if we cloned/fetched without a mirror; otherwise it may be anything.
495         $self->exe('git', 'config', 'remote.origin.url', $url);
496     }
497
498     my $template = getcwd()."/../.commit-template";
499     if (-e $template) {
500         $self->exe('git', 'config', 'commit.template', $template);
501     }
502
503     $self->git_add_remotes($repo_basename);
504
505     if ($self->{'detach-alternates'}) {
506         $self->exe('git', 'repack', '-a');
507
508         my $alternates_path = '.git/objects/info/alternates';
509         if (-e $alternates_path) {
510             unlink($alternates_path) || confess "unlink $alternates_path: $OS_ERROR";
511         }
512     }
513
514     chdir($orig_cwd) or confess "cd $orig_cwd: $OS_ERROR";
515
516     return;
517 }
518
519 sub ensure_link
520 {
521     my ($self, $src, $tgt) = @_;
522     return if (!$self->{'force-hooks'} and -f $tgt);
523     unlink($tgt); # In case we have a dead symlink or pre-existing hook
524     print "Aliasing $src\n      as $tgt ...\n" if (!$self->{quiet});
525     return if eval { symlink($src, $tgt) };
526     # Windows doesn't do (proper) symlinks. As the post_commit script needs
527     # them to locate itself, we write a forwarding script instead.
528     open SCRIPT, ">".$tgt or die "Cannot create forwarding script $tgt: $!\n";
529     print SCRIPT "#!/bin/sh\nexec `dirname \$0`/$src \"\$\@\"\n";
530     close SCRIPT;
531 }
532
533 sub git_install_hooks
534 {
535     my ($self) = @_;
536
537     return if (!-d 'qtrepotools/git-hooks');
538
539     # Force C locale as git submodule returns the localized string "Entering"
540     local $ENV{LC_ALL} = 'C';
541     chomp(my @modules = `git submodule foreach :`);
542     push @modules, "";
543     for my $module (@modules) {
544         $module =~ s,^Entering \'([^\']+)\'$,$1/,;
545         my $rel = $module;
546         $rel =~ s,[^/]+,..,g;
547         $rel .= "../../qtrepotools/git-hooks/";
548         $self->ensure_link($rel.'gerrit_commit_msg_hook', $module.'.git/hooks/commit-msg');
549         $self->ensure_link($rel.'git_post_commit_hook', $module.'.git/hooks/post-commit');
550     }
551 }
552
553 sub run
554 {
555     my ($self) = @_;
556
557     $self->check_if_already_initialized;
558
559     chomp(my $url = `git config remote.origin.url`);
560     die("Have no origin remote.\n") if (!$url);
561     $url =~ s,\.git$,,;
562     $url =~ s,qt/qt5$,,;
563     $self->{'base-url'} = $url;
564
565     $self->git_clone_all_submodules('qt/qt5', $self->{branch}, @{$self->{'module-subset'}});
566
567     $self->git_add_remotes('qt/qt5');
568
569     $self->git_install_hooks;
570
571     return;
572 }
573
574 #==============================================================================
575
576 Qt::InitRepository->new(@ARGV)->run if (!caller);
577 1;