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