Update submodules on '5.6' in qt5
[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 sub printUsage($)
43 {
44     my ($ex) = @_;
45
46     print <<EOF ;
47 Usage:
48       ./init-repository [options]
49
50     This script may be run after an initial `git clone' of Qt5 in order to
51     check out all submodules. It fetches them from canonical URLs inferred
52     from the clone's origin.
53
54 Options:
55   Global options:
56
57     --force, -f
58         Force initialization (even if the submodules are already checked
59         out).
60
61     --force-hooks
62         Force initialization of hooks (even if there are already hooks in
63         checked out submodules).
64
65     --quiet, -q
66         Be quiet. Will exit cleanly if the repository is already
67         initialized.
68
69   Module options:
70
71     --module-subset=<module1>,<module2>...
72         Only initialize the specified subset of modules given as the
73         argument. Specified modules must already exist in .gitmodules. The
74         string "all" results in cloning all known modules. The strings
75         "essential", "addon", "preview", "deprecated", "obsolete", and
76         "ignore" refer to classes of modules; "default" maps to
77         "essential,addon,preview,deprecated", which corresponds with the
78         set of maintained modules and is also the default set. Module
79         names may be prefixed with a dash to exclude them from a bigger
80         set, e.g. "all,-ignore".
81
82     --no-update
83         Skip the `git submodule update' command.
84
85     --no-fetch
86         Skip the `git fetch' commands. Implied by --no-update.
87
88     --branch
89         Instead of checking out specific SHA1s, check out the submodule
90         branches that correspond with the current supermodule commit. By
91         default, this option will cause local commits in the submodules to
92         be rebased. With --no-update, the branches will be checked out, but
93         their heads will not move.
94
95     --ignore-submodules
96         Set git config to ignore submodules by default when doing operations
97         on the qt5 repo, such as `pull', `fetch', `diff' etc.
98
99         After using this option, pass `--ignore-submodules=none' to git to
100         override it as needed.
101
102   Repository options:
103
104     --berlin
105         Switch to internal URLs and make use of the Berlin git mirrors.
106         (Implies `--mirror').
107
108     --oslo
109         Switch to internal URLs and make use of the Oslo git mirrors.
110         (Implies `--mirror').
111
112     --codereview-username <Gerrit/JIRA username>
113         Specify the user name for the (potentially) writable `gerrit' remote
114         for each module, for use with the Gerrit code review tool.
115
116         If this option is omitted, the gerrit remote is created without a
117         username and port number, and thus relies on a correct SSH
118         configuration.
119
120     --alternates <path to other Qt5 repo>
121         Adds alternates for each submodule to another full qt5 checkout.
122         This makes this qt5 checkout very small, as it will use the object
123         store of the alternates before unique objects are stored in its own
124         object store.
125
126         This option has no effect when using `--no-update'.
127
128         NOTE: This will make this repo dependent on the alternate, which is
129         potentially dangerous! The dependency can be broken by also using
130         the `--copy-objects' option, or by running "git repack -a" in each
131         submodule, where required. Please read the note about the `--shared'
132         option in the documentation of `git clone' for more information.
133
134     --copy-objects
135         When `--alternates' is used, automatically do a "git repack -a" in
136         each submodule after cloning, to ensure that the repositories are
137         independent from the source used as a reference for cloning.
138
139         Note that this negates the disk usage benefits gained from the use
140         of `--alternates'.
141
142     --mirror <url-base>
143         Uses <url-base> as the base URL for submodule git mirrors.
144
145         For example:
146
147           --mirror user\@machine:/foo/bar/qt/
148
149         ...will use the following as a mirror for qtbase:
150
151           user\@machine:/foo/bar/qt/qtbase.git
152
153         The mirror is permitted to contain a subset of the submodules; any
154         missing modules will fall back to the canonical URLs.
155
156 EOF
157     exit($ex);
158 }
159
160 use Carp         qw( confess             );
161 use Cwd          qw( getcwd abs_path     );
162 use English      qw( -no_match_vars      );
163 use File::Spec::Functions qw ( rel2abs   );
164 use Getopt::Long qw( GetOptions          );
165
166 my $script_path = abs_path($0);
167 $script_path =~ s,[/\\][^/\\]+$,,;
168
169 my $GERRIT_SSH_BASE
170     = 'ssh://@USER@codereview.qt-project.org@PORT@/qt/';
171
172 my $BER_MIRROR_URL_BASE
173     = 'git://hegel/qt/';
174
175 my $OSLO_MIRROR_URL_BASE
176     = 'git://qilin/qt/';
177
178 sub new
179 {
180     my ($class, @arguments) = @_;
181
182     my $self = {};
183     bless $self, $class;
184     $self->parse_arguments(@arguments);
185
186     return $self;
187 }
188
189 # Like `system', but possibly log the command, and die on non-zero exit code
190 sub exe
191 {
192     my ($self, @cmd) = @_;
193
194     if (!$self->{quiet}) {
195         print "+ @cmd\n";
196     }
197
198     if (system(@cmd) != 0) {
199         confess "@cmd exited with status $CHILD_ERROR";
200     }
201
202     return;
203 }
204
205 sub parse_arguments
206 {
207     my ($self) = @_;
208
209     %{$self} = (%{$self},
210         'alternates'          => "",
211         'branch'              => 0,
212         'codereview-username' => "",
213         'detach-alternates'   => 0 ,
214         'force'               => 0 ,
215         'force-hooks'         => 0 ,
216         'ignore-submodules'   => 0 ,
217         'mirror-url'          => "",
218         'update'              => 1 ,
219         'fetch'               => 1 ,
220         'module-subset'       => "default",
221     );
222
223     GetOptions(
224         'alternates=s'      =>  \$self->{qw{ alternates        }},
225         'branch'            =>  \$self->{qw{ branch            }},
226         'codereview-username=s' => \$self->{qw{ codereview-username }},
227         'copy-objects'      =>  \$self->{qw{ detach-alternates }},
228         'force|f'           =>  \$self->{qw{ force             }},
229         'force-hooks'       =>  \$self->{qw{ force-hooks       }},
230         'ignore-submodules' =>  \$self->{qw{ ignore-submodules }},
231         'mirror=s'          =>  \$self->{qw{ mirror-url        }},
232         'quiet'             =>  \$self->{qw{ quiet             }},
233         'update!'           =>  \$self->{qw{ update            }},
234         'fetch!'            =>  \$self->{qw{ fetch             }},
235         'module-subset=s'   =>  \$self->{qw{ module-subset     }},
236
237         'help|?'            =>  sub { printUsage(1);            },
238
239         'berlin' => sub {
240             $self->{'mirror-url'}        = $BER_MIRROR_URL_BASE;
241         },
242         'oslo' => sub {
243             $self->{'mirror-url'}        = $OSLO_MIRROR_URL_BASE;
244         },
245     ) || printUsage(2);
246
247     # Replace any double trailing slashes from end of mirror
248     $self->{'mirror-url'} =~ s{//+$}{/};
249
250     $self->{'module-subset'} =~ s/\bdefault\b/preview,essential,addon,deprecated/;
251     $self->{'module-subset'} = [ split(/,/, $self->{'module-subset'}) ];
252
253     $self->{'fetch'} = 0 if (!$self->{'update'});
254
255     return;
256 }
257
258 sub check_if_already_initialized
259 {
260     my ($self) = @_;
261
262     # We consider the repo as `initialized' if submodule.qtbase.url is set
263     if (qx(git config --get submodule.qtbase.url)) {
264         if (!$self->{force}) {
265             exit 0 if ($self->{quiet});
266             print "Will not reinitialize already initialized repository (use -f to force)!\n";
267             exit 1;
268         }
269     }
270
271     return;
272 }
273
274 sub git_submodule_init
275 {
276     my ($self, @init_args) = @_;
277
278     if ($self->{quiet}) {
279         unshift @init_args, '--quiet';
280     }
281     $self->exe('git', 'submodule', 'init', @init_args);
282
283     my $template = getcwd()."/.commit-template";
284     if (-e $template) {
285         $self->exe('git', 'config', 'commit.template', $template);
286     }
287
288     return;
289 }
290
291 use constant {
292     STS_PREVIEW => 1,
293     STS_ESSENTIAL => 2,
294     STS_ADDON => 3,
295     STS_DEPRECATED => 4,
296     STS_OBSOLETE => 5
297 };
298
299 sub git_clone_all_submodules
300 {
301     my ($self, $my_repo_base, $co_branch, @subset) = @_;
302
303     my %subdirs = ();
304     my %subbranches = ();
305     my %subbases = ();
306     my %subinits = ();
307     my @submodconfig = qx(git config -l -f .gitmodules);
308     foreach my $line (@submodconfig) {
309         # Example line: submodule.qtqa.url=../qtqa.git
310         next if ($line !~ /^submodule\.([^.=]+)\.([^.=]+)=(.*)$/);
311         if ($2 eq "path") {
312             $subdirs{$1} = $3;
313         } elsif ($2 eq "branch") {
314             $subbranches{$1} = $3;
315         } elsif ($2 eq "url") {
316             my ($mod, $base) = ($1, $3);
317             next if ($base !~ /^\.\.\//);
318             $base = $my_repo_base.'/'.$base;
319             while ($base =~ s,(?!\.\./)[^/]+/\.\./,,g) {}
320             $subbases{$mod} = $base;
321         } elsif ($2 eq "update") {
322             push @subset, '-'.$1 if ($3 eq 'none');
323         } elsif ($2 eq "status") {
324             if ($3 eq "preview") {
325                 $subinits{$1} = STS_PREVIEW;
326             } elsif ($3 eq "essential") {
327                 $subinits{$1} = STS_ESSENTIAL;
328             } elsif ($3 eq "addon") {
329                 $subinits{$1} = STS_ADDON;
330             } elsif ($3 eq "deprecated") {
331                 $subinits{$1} = STS_DEPRECATED;
332             } elsif ($3 eq "obsolete") {
333                 $subinits{$1} = STS_OBSOLETE;
334             } elsif ($3 eq "ignore") {
335                 delete $subinits{$1};
336             } else {
337                 die("Invalid subrepo status '$3' for '$1'.\n");
338             }
339         }
340     }
341
342     my %include = ();
343     foreach my $mod (@subset) {
344         my $del = ($mod =~ s/^-//);
345         my $fail = 0;
346         my @what;
347         if ($mod eq "all") {
348             @what = keys %subbases;
349         } elsif ($mod eq "essential") {
350             @what = grep { ($subinits{$_} || 0) eq STS_ESSENTIAL } keys %subbases;
351         } elsif ($mod eq "addon") {
352             @what = grep { ($subinits{$_} || 0) eq STS_ADDON } keys %subbases;
353         } elsif ($mod eq "preview") {
354             @what = grep { ($subinits{$_} || 0) eq STS_PREVIEW } keys %subbases;
355         } elsif ($mod eq "deprecated") {
356             @what = grep { ($subinits{$_} || 0) eq STS_DEPRECATED } keys %subbases;
357         } elsif ($mod eq "obsolete") {
358             @what = grep { ($subinits{$_} || 0) eq STS_OBSOLETE } keys %subbases;
359         } elsif ($mod eq "ignore") {
360             @what = grep { ($subinits{$_} || 0) eq 0 } keys %subbases;
361         } elsif (defined($subdirs{$mod})) {
362             push @what, $mod;
363         } else {
364             $fail = 1;
365         }
366         if ($del) {
367             print "Warning: excluding non-existent module '$mod'.\n"
368                 if ($fail);
369             map { delete $include{$_} } @what;
370         } else {
371             die("Error: module subset names non-existent '$mod'.\n")
372                 if ($fail);
373             map { $include{$_} = 1; } @what;
374         }
375     }
376
377     my @modules = sort keys %include;
378
379     $self->git_submodule_init(map { $subdirs{$_} } @modules);
380
381     # manually clone each repo here, so we can easily use reference repos, mirrors etc
382     my @configresult = qx(git config -l);
383     foreach my $line (@configresult) {
384         # Example line: submodule.qtqa.url=git://code.qt.io/qt/qtqa.git
385         next if ($line !~ /submodule\.([^.=]+)\.url=/);
386         my $module = $1;
387
388         if (!defined($include{$module})) {
389             $self->exe('git', 'config', '--remove-section', "submodule.$module");
390             next;
391         }
392
393         if ($self->{'ignore-submodules'}) {
394             $self->exe('git', 'config', "submodule.$module.ignore", 'all');
395         }
396     }
397
398     my $any_bad = 0;
399     foreach my $module (@modules) {
400         $any_bad = 1
401             if ($self->git_stat_one_submodule($subdirs{$module}));
402     }
403     die("Dirty submodule(s) present; cannot proceed.\n")
404         if ($any_bad);
405
406     foreach my $module (@modules) {
407         $self->git_clone_one_submodule($subdirs{$module}, $subbases{$module},
408                                        $co_branch && $subbranches{$module});
409     }
410
411     if ($co_branch) {
412         foreach my $module (@modules) {
413             my $branch = $subbranches{$module};
414             die("No branch defined for submodule $module.\n") if (!defined($branch));
415             my $orig_cwd = getcwd();
416             chdir($module) or confess "chdir $module: $OS_ERROR";
417             my $br = qx(git rev-parse -q --verify $branch);
418             if (!$br) {
419                 $self->exe('git', 'checkout', '-b', $branch, "origin/$branch");
420             } else {
421                 $self->exe('git', 'checkout', $branch);
422             }
423             chdir("$orig_cwd") or confess "chdir $orig_cwd: $OS_ERROR";
424         }
425     }
426     if ($self->{update}) {
427         my @cmd = ('git', 'submodule', 'update', '--force', '--no-fetch');
428         push @cmd, '--remote', '--rebase' if ($co_branch);
429         $self->exe(@cmd);
430
431         foreach my $module (@modules) {
432             if (-f $module.'/.gitmodules') {
433                 my $orig_cwd = getcwd();
434                 chdir($module) or confess "chdir $module: $OS_ERROR";
435                 $self->git_clone_all_submodules($subbases{$module}, 0, "all");
436                 chdir("$orig_cwd") or confess "chdir $orig_cwd: $OS_ERROR";
437             }
438         }
439     }
440
441     return;
442 }
443
444 sub git_add_remotes
445 {
446     my ($self, $gerrit_repo_basename) = @_;
447
448     my $gerrit_repo_url = $GERRIT_SSH_BASE;
449     # If given a username, make a "verbose" remote.
450     # Otherwise, rely on proper SSH configuration.
451     if ($self->{'codereview-username'}) {
452         $gerrit_repo_url =~ s,\@USER\@,$self->{'codereview-username'}\@,;
453         $gerrit_repo_url =~ s,\@PORT\@,:29418,;
454     } else {
455         $gerrit_repo_url =~ s,\@[^\@]+\@,,g;
456     }
457
458     $gerrit_repo_url .= $gerrit_repo_basename;
459     $self->exe('git', 'config', 'remote.gerrit.url', $gerrit_repo_url);
460     $self->exe('git', 'config', 'remote.gerrit.fetch', '+refs/heads/*:refs/remotes/gerrit/*', '/heads/');
461 }
462
463 sub git_stat_one_submodule
464 {
465     my ($self, $submodule) = @_;
466
467     return 0 if (! -e "$submodule/.git");
468
469     my $orig_cwd = getcwd();
470     chdir($submodule) or confess "chdir $submodule: $OS_ERROR";
471
472     my @sts = qx(git status --porcelain --untracked=no --ignore-submodules=all);
473
474     # After a git clone --no-checkout, git status reports all files as
475     # staged for deletion, but we still want to update the submodule.
476     # It's unlikely that a genuinely dirty index would have _only_ this
477     # type of modifications, and it doesn't seem like a horribly big deal
478     # to lose them anyway, so ignore them.
479     @sts = grep(!/^D  /, @sts);
480
481     chdir($orig_cwd) or confess "cd $orig_cwd: $OS_ERROR";
482
483     return 0 if (!@sts);
484
485     print STDERR "$submodule is dirty.\n";
486
487     return -1;
488 }
489
490 sub git_clone_one_submodule
491 {
492     my ($self, $submodule, $repo_basename, $branch) = @_;
493
494     my $alternates            = $self->{ 'alternates'        };
495     my $mirror_url            = $self->{ 'mirror-url'        };
496     my $protocol              = $self->{ 'protocol'          };
497
498     # `--reference FOO' args for the clone, if any.
499     my @reference_args;
500
501     if ($alternates) {
502         # alternates is a qt5 repo, so the submodule will be under that.
503         if (-e "$alternates/$submodule/.git") {
504             @reference_args = ('--reference', "$alternates/$submodule");
505         }
506         else {
507             print " *** $alternates/$submodule not found, ignoring alternate for this submodule\n";
508         }
509     }
510
511     my $do_clone = (! -e "$submodule/.git");
512
513     my $url = $self->{'base-url'}.$repo_basename;
514     my $mirror;
515     if ($mirror_url && ($do_clone || $self->{fetch})) {
516         $mirror = $mirror_url.$repo_basename;
517     }
518
519     if ($mirror) {
520         # Only use the mirror if it can be reached.
521         eval { $self->exe('git', 'ls-remote', $mirror, 'test/if/mirror/exists') };
522         if ($@) {
523             warn "mirror [$mirror] is not accessible; $url will be used\n";
524             undef $mirror;
525         }
526     }
527
528     if ($do_clone) {
529         if ($branch) {
530             push @reference_args, '--branch', $branch;
531         } else {
532             push @reference_args, '--no-checkout';
533         }
534         $self->exe('git', 'clone', @reference_args,
535                    ($mirror ? $mirror : $url), $submodule);
536     }
537
538     my $orig_cwd = getcwd();
539     chdir($submodule) or confess "chdir $submodule: $OS_ERROR";
540
541     if ($mirror) {
542         # This is only for the user's convenience - we make no use of it.
543         $self->exe('git', 'config', 'remote.mirror.url', $mirror);
544         $self->exe('git', 'config', 'remote.mirror.fetch', '+refs/heads/*:refs/remotes/mirror/*');
545     }
546
547     if (!$do_clone && $self->{fetch}) {
548         # If we didn't clone, fetch from the right location. We always update
549         # the origin remote, so that submodule update --remote works.
550         $self->exe('git', 'config', 'remote.origin.url', ($mirror ? $mirror : $url));
551         $self->exe('git', 'fetch', 'origin');
552     }
553
554     if (!($do_clone || $self->{fetch}) || $mirror) {
555         # Leave the origin configured to the canonical URL. It's already correct
556         # if we cloned/fetched without a mirror; otherwise it may be anything.
557         $self->exe('git', 'config', 'remote.origin.url', $url);
558     }
559
560     my $template = $orig_cwd."/.commit-template";
561     if (-e $template) {
562         $self->exe('git', 'config', 'commit.template', $template);
563     }
564
565     $self->git_add_remotes($repo_basename);
566
567     if ($self->{'detach-alternates'}) {
568         $self->exe('git', 'repack', '-a');
569
570         my $alternates_path = '.git/objects/info/alternates';
571         if (-e $alternates_path) {
572             unlink($alternates_path) || confess "unlink $alternates_path: $OS_ERROR";
573         }
574     }
575
576     chdir($orig_cwd) or confess "cd $orig_cwd: $OS_ERROR";
577
578     return;
579 }
580
581 sub ensure_link
582 {
583     my ($self, $src, $tgt) = @_;
584     return if (!$self->{'force-hooks'} and -f $tgt);
585     unlink($tgt); # In case we have a dead symlink or pre-existing hook
586     print "Aliasing $src\n      as $tgt ...\n" if (!$self->{quiet});
587     if ($^O ne "msys" && $^O ne "MSWin32") {
588         return if eval { symlink($src, $tgt) };
589     }
590     # Windows doesn't do (proper) symlinks. As the post_commit script needs
591     # them to locate itself, we write a forwarding script instead.
592     open SCRIPT, ">".$tgt or die "Cannot create forwarding script $tgt: $!\n";
593     # Make the path palatable for MSYS.
594     $src =~ s,\\,/,g;
595     $src =~ s,^(.):/,/$1/,g;
596     print SCRIPT "#!/bin/sh\nexec $src \"\$\@\"\n";
597     close SCRIPT;
598 }
599
600 sub git_install_hooks
601 {
602     my ($self) = @_;
603
604     my $hooks = $script_path.'/qtrepotools/git-hooks';
605     return if (!-d $hooks);
606
607     my @configresult = qx(git config --list --local);
608     foreach my $line (@configresult) {
609         next if ($line !~ /submodule\.([^.=]+)\.url=/);
610         my $module = $1;
611         my $module_gitdir = $module.'/.git';
612         if (!-d $module_gitdir) {
613             open GITD, $module_gitdir or die "Cannot open $module: $!\n";
614             my $gd = <GITD>;
615             close GITD;
616             chomp($gd);
617             $gd =~ s/^gitdir: // or die "Malformed .git file $module_gitdir\n";
618             $module_gitdir = rel2abs($gd, $module);
619             if (open COMD, $module_gitdir.'/commondir') {
620                 my $cd = <COMD>;
621                 chomp($cd);
622                 $module_gitdir .= '/'.$cd;
623                 $module_gitdir = abs_path($module_gitdir);
624                 close COMD;
625             }
626         }
627         $self->ensure_link($hooks.'/gerrit_commit_msg_hook', $module_gitdir.'/hooks/commit-msg');
628         $self->ensure_link($hooks.'/git_post_commit_hook', $module_gitdir.'/hooks/post-commit');
629     }
630 }
631
632 sub run
633 {
634     my ($self) = @_;
635
636     $self->check_if_already_initialized;
637
638     chomp(my $url = `git config remote.origin.url`);
639     die("Have no origin remote.\n") if (!$url);
640     $url =~ s,\.git$,,;
641     $url =~ s/qt5$//;
642     $self->{'base-url'} = $url;
643
644     $self->git_clone_all_submodules('qt5', $self->{branch}, @{$self->{'module-subset'}});
645
646     $self->git_add_remotes('qt5');
647
648     $self->git_install_hooks;
649
650     return;
651 }
652
653 #==============================================================================
654
655 Qt::InitRepository->new()->run if (!caller);
656 1;