fix cloning of tagged versions
[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},
393                                        $co_branch && $subbranches{$module});
394     }
395
396     if ($co_branch) {
397         foreach my $module (@modules) {
398             my $branch = $subbranches{$module};
399             die("No branch defined for submodule $module.\n") if (!defined($branch));
400             my $orig_cwd = getcwd();
401             chdir($module) or confess "chdir $module: $OS_ERROR";
402             my $br = qx(git rev-parse -q --verify $branch);
403             if (!$br) {
404                 $self->exe('git', 'checkout', '-b', $branch, "origin/$branch");
405             } else {
406                 $self->exe('git', 'checkout', $branch);
407             }
408             chdir("$orig_cwd") or confess "chdir $orig_cwd: $OS_ERROR";
409         }
410     }
411     if ($self->{update}) {
412         my @cmd = ('git', 'submodule', 'update', '--no-fetch');
413         push @cmd, '--remote', '--rebase' if ($co_branch);
414         $self->exe(@cmd);
415
416         foreach my $module (@modules) {
417             if (-f $module.'/.gitmodules') {
418                 my $orig_cwd = getcwd();
419                 chdir($module) or confess "chdir $module: $OS_ERROR";
420                 $self->git_clone_all_submodules($subbases{$module}, 0, "all");
421                 chdir("$orig_cwd") or confess "chdir $orig_cwd: $OS_ERROR";
422             }
423         }
424     }
425
426     return;
427 }
428
429 sub git_add_remotes
430 {
431     my ($self, $gerrit_repo_basename) = @_;
432
433     my $gerrit_repo_url = $GERRIT_SSH_BASE;
434     # If given a username, make a "verbose" remote.
435     # Otherwise, rely on proper SSH configuration.
436     if ($self->{'codereview-username'}) {
437         $gerrit_repo_url =~ s,\@USER\@,$self->{'codereview-username'}\@,;
438         $gerrit_repo_url =~ s,\@PORT\@,:29418,;
439     } else {
440         $gerrit_repo_url =~ s,\@[^\@]+\@,,g;
441     }
442
443     $gerrit_repo_url .= $gerrit_repo_basename;
444     $self->exe('git', 'config', 'remote.gerrit.url', $gerrit_repo_url);
445     $self->exe('git', 'config', 'remote.gerrit.fetch', '+refs/heads/*:refs/remotes/gerrit/*', '/heads/');
446 }
447
448 sub git_clone_one_submodule
449 {
450     my ($self, $submodule, $repo_basename, $branch) = @_;
451
452     my $alternates            = $self->{ 'alternates'        };
453     my $mirror_url            = $self->{ 'mirror-url'        };
454     my $protocol              = $self->{ 'protocol'          };
455
456     # `--reference FOO' args for the clone, if any.
457     my @reference_args;
458
459     if ($alternates) {
460         # alternates is a qt5 repo, so the submodule will be under that.
461         if (-e "$alternates/$submodule/.git") {
462             @reference_args = ('--reference', "$alternates/$submodule");
463         }
464         else {
465             print " *** $alternates/$submodule not found, ignoring alternate for this submodule\n";
466         }
467     }
468
469     my $url = $self->{'base-url'}.$repo_basename;
470     my $mirror;
471     if ($mirror_url) {
472         $mirror = $mirror_url.$repo_basename;
473     }
474
475     if ($mirror) {
476         # Only use the mirror if it can be reached.
477         eval { $self->exe('git', 'ls-remote', $mirror, 'test/if/mirror/exists') };
478         if ($@) {
479             warn "mirror [$mirror] is not accessible; $url will be used\n";
480             undef $mirror;
481         }
482     }
483
484     my $do_clone = (! -e "$submodule/.git");
485     if ($do_clone) {
486         if ($branch) {
487             push @reference_args, '--branch', $branch;
488         } else {
489             push @reference_args, '--no-checkout';
490         }
491         $self->exe('git', 'clone', @reference_args,
492                    ($mirror ? $mirror : $url), $submodule);
493     }
494
495     my $orig_cwd = getcwd();
496     chdir($submodule) or confess "chdir $submodule: $OS_ERROR";
497
498     if ($mirror) {
499         # This is only for the user's convenience - we make no use of it.
500         $self->exe('git', 'config', 'remote.mirror.url', $mirror);
501         $self->exe('git', 'config', 'remote.mirror.fetch', '+refs/heads/*:refs/remotes/mirror/*');
502     }
503
504     if (!$do_clone && $self->{update}) {
505         # If we didn't clone, fetch from the right location. We always update
506         # the origin remote, so that submodule update --remote works.
507         $self->exe('git', 'config', 'remote.origin.url', ($mirror ? $mirror : $url));
508         $self->exe('git', 'fetch', 'origin');
509     }
510
511     if (!($do_clone || $self->{update}) || $mirror) {
512         # Leave the origin configured to the canonical URL. It's already correct
513         # if we cloned/fetched without a mirror; otherwise it may be anything.
514         $self->exe('git', 'config', 'remote.origin.url', $url);
515     }
516
517     my $template = getcwd()."/../.commit-template";
518     if (-e $template) {
519         $self->exe('git', 'config', 'commit.template', $template);
520     }
521
522     $self->git_add_remotes($repo_basename);
523
524     if ($self->{'detach-alternates'}) {
525         $self->exe('git', 'repack', '-a');
526
527         my $alternates_path = '.git/objects/info/alternates';
528         if (-e $alternates_path) {
529             unlink($alternates_path) || confess "unlink $alternates_path: $OS_ERROR";
530         }
531     }
532
533     chdir($orig_cwd) or confess "cd $orig_cwd: $OS_ERROR";
534
535     return;
536 }
537
538 sub ensure_link
539 {
540     my ($self, $src, $tgt) = @_;
541     return if (!$self->{'force-hooks'} and -f $tgt);
542     unlink($tgt); # In case we have a dead symlink or pre-existing hook
543     print "Aliasing $src\n      as $tgt ...\n" if (!$self->{quiet});
544     if ($^O ne "msys" && $^O ne "MSWin32") {
545         return if eval { symlink($src, $tgt) };
546     }
547     # Windows doesn't do (proper) symlinks. As the post_commit script needs
548     # them to locate itself, we write a forwarding script instead.
549     open SCRIPT, ">".$tgt or die "Cannot create forwarding script $tgt: $!\n";
550     # Make the path palatable for MSYS.
551     $src =~ s,\\,/,g;
552     $src =~ s,^(.):/,/$1/,g;
553     print SCRIPT "#!/bin/sh\nexec $src \"\$\@\"\n";
554     close SCRIPT;
555 }
556
557 sub git_install_hooks
558 {
559     my ($self) = @_;
560
561     my $hooks = $script_path.'/qtrepotools/git-hooks';
562     return if (!-d $hooks);
563
564     my @configresult = qx(git config --list --local);
565     foreach my $line (@configresult) {
566         next if ($line !~ /submodule\.([^.=]+)\.url=/);
567         my $module = $1;
568         my $module_gitdir = $module.'/.git';
569         if (!-d $module_gitdir) {
570             open GITD, $module_gitdir or die "Cannot open $module: $!\n";
571             my $gd = <GITD>;
572             close GITD;
573             chomp($gd);
574             $gd =~ s/^gitdir: // or die "Malformed .git file $module_gitdir\n";
575             $module_gitdir = rel2abs($gd, $module);
576             if (open COMD, $module_gitdir.'/commondir') {
577                 my $cd = <COMD>;
578                 chomp($cd);
579                 $module_gitdir .= '/'.$cd;
580                 $module_gitdir = abs_path($module_gitdir);
581                 close COMD;
582             }
583         }
584         $self->ensure_link($hooks.'/gerrit_commit_msg_hook', $module_gitdir.'/hooks/commit-msg');
585         $self->ensure_link($hooks.'/git_post_commit_hook', $module_gitdir.'/hooks/post-commit');
586     }
587 }
588
589 sub run
590 {
591     my ($self) = @_;
592
593     $self->check_if_already_initialized;
594
595     chomp(my $url = `git config remote.origin.url`);
596     die("Have no origin remote.\n") if (!$url);
597     $url =~ s,\.git$,,;
598     $url =~ s,qt/qt5$,,;
599     $self->{'base-url'} = $url;
600
601     $self->git_clone_all_submodules('qt/qt5', $self->{branch}, @{$self->{'module-subset'}});
602
603     $self->git_add_remotes('qt/qt5');
604
605     $self->git_install_hooks;
606
607     return;
608 }
609
610 #==============================================================================
611
612 Qt::InitRepository->new()->run if (!caller);
613 1;