2 #############################################################################
4 ## Copyright (C) 2015 The Qt Company Ltd.
5 ## Contact: http://www.qt.io/licensing/
7 ## This file is part of the utilities of the Qt Toolkit.
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.
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.
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.
33 #############################################################################
39 package Qt::InitRepository;
48 ./init-repository [options]
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.
58 Force initialization (even if the submodules are already checked
62 Force initialization of hooks (even if there are already hooks in
63 checked out submodules).
66 Be quiet. Will exit cleanly if the repository is already
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".
83 Skip the `git submodule update' command.
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.
93 Set git config to ignore submodules by default when doing operations
94 on the qt5 repo, such as `pull', `fetch', `diff' etc.
96 After using this option, pass `--ignore-submodules=none' to git to
97 override it as needed.
102 Switch to internal URLs and make use of the Berlin git mirrors.
103 (Implies `--mirror').
106 Switch to internal URLs and make use of the Oslo git mirrors.
107 (Implies `--mirror').
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.
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
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
123 This option has no effect when using `--no-update'.
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.
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.
136 Note that this negates the disk usage benefits gained from the use
140 Uses <url-base> as the base URL for submodule git mirrors.
144 --mirror user\@machine:/foo/bar/
146 ...will use the following as a mirror for qtbase:
148 user\@machine:/foo/bar/qt/qtbase.git
150 The mirror is permitted to contain a subset of the submodules; any
151 missing modules will fall back to the canonical URLs.
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 );
163 my $script_path = abs_path($0);
164 $script_path =~ s,[/\\][^/\\]+$,,;
167 = 'ssh://@USER@codereview.qt-project.org@PORT@/';
169 my $BER_MIRROR_URL_BASE
172 my $OSLO_MIRROR_URL_BASE
177 my ($class, @arguments) = @_;
181 $self->parse_arguments(@arguments);
186 # Like `system', but possibly log the command, and die on non-zero exit code
189 my ($self, @cmd) = @_;
191 if (!$self->{quiet}) {
195 if (system(@cmd) != 0) {
196 confess "@cmd exited with status $CHILD_ERROR";
206 %{$self} = (%{$self},
209 'codereview-username' => "",
210 'detach-alternates' => 0 ,
213 'ignore-submodules' => 0 ,
216 'module-subset' => "default",
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 }},
232 'help|?' => sub { printUsage(1); },
235 $self->{'mirror-url'} = $BER_MIRROR_URL_BASE;
238 $self->{'mirror-url'} = $OSLO_MIRROR_URL_BASE;
242 # Replace any double trailing slashes from end of mirror
243 $self->{'mirror-url'} =~ s{//+$}{/};
245 $self->{'module-subset'} =~ s/\bdefault\b/preview,essential,addon,deprecated/;
246 $self->{'module-subset'} = [ split(/,/, $self->{'module-subset'}) ];
251 sub check_if_already_initialized
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";
267 sub git_submodule_init
269 my ($self, @init_args) = @_;
271 if ($self->{quiet}) {
272 unshift @init_args, '--quiet';
274 $self->exe('git', 'submodule', 'init', @init_args);
276 my $template = getcwd()."/.commit-template";
278 $self->exe('git', 'config', 'commit.template', $template);
292 sub git_clone_all_submodules
294 my ($self, $my_repo_base, $co_branch, @subset) = @_;
297 my %subbranches = ();
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\.([^.=]+)\.([^.=]+)=(.*)$/);
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};
330 die("Invalid subrepo status '$3' for '$1'.\n");
336 foreach my $mod (@subset) {
337 my $del = ($mod =~ s/^-//);
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})) {
360 print "Warning: excluding non-existent module '$mod'.\n"
362 map { delete $include{$_} } @what;
364 die("Error: module subset names non-existent '$mod'.\n")
366 map { $include{$_} = 1; } @what;
370 my @modules = sort keys %include;
372 $self->git_submodule_init(map { $subdirs{$_} } @modules);
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=/);
381 if (!defined($include{$module})) {
382 $self->exe('git', 'config', '--remove-section', "submodule.$module");
386 if ($self->{'ignore-submodules'}) {
387 $self->exe('git', 'config', "submodule.$module.ignore", 'all');
391 foreach my $module (@modules) {
392 $self->git_clone_one_submodule($subdirs{$module}, $subbases{$module},
393 $co_branch && $subbranches{$module});
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);
404 $self->exe('git', 'checkout', '-b', $branch, "origin/$branch");
406 $self->exe('git', 'checkout', $branch);
408 chdir("$orig_cwd") or confess "chdir $orig_cwd: $OS_ERROR";
411 if ($self->{update}) {
412 my @cmd = ('git', 'submodule', 'update', '--no-fetch');
413 push @cmd, '--remote', '--rebase' if ($co_branch);
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";
431 my ($self, $gerrit_repo_basename) = @_;
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,;
440 $gerrit_repo_url =~ s,\@[^\@]+\@,,g;
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/');
448 sub git_clone_one_submodule
450 my ($self, $submodule, $repo_basename, $branch) = @_;
452 my $alternates = $self->{ 'alternates' };
453 my $mirror_url = $self->{ 'mirror-url' };
454 my $protocol = $self->{ 'protocol' };
456 # `--reference FOO' args for the clone, if any.
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");
465 print " *** $alternates/$submodule not found, ignoring alternate for this submodule\n";
469 my $url = $self->{'base-url'}.$repo_basename;
472 $mirror = $mirror_url.$repo_basename;
476 # Only use the mirror if it can be reached.
477 eval { $self->exe('git', 'ls-remote', $mirror, 'test/if/mirror/exists') };
479 warn "mirror [$mirror] is not accessible; $url will be used\n";
484 my $do_clone = (! -e "$submodule/.git");
487 push @reference_args, '--branch', $branch;
489 push @reference_args, '--no-checkout';
491 $self->exe('git', 'clone', @reference_args,
492 ($mirror ? $mirror : $url), $submodule);
495 my $orig_cwd = getcwd();
496 chdir($submodule) or confess "chdir $submodule: $OS_ERROR";
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/*');
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');
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);
517 my $template = getcwd()."/../.commit-template";
519 $self->exe('git', 'config', 'commit.template', $template);
522 $self->git_add_remotes($repo_basename);
524 if ($self->{'detach-alternates'}) {
525 $self->exe('git', 'repack', '-a');
527 my $alternates_path = '.git/objects/info/alternates';
528 if (-e $alternates_path) {
529 unlink($alternates_path) || confess "unlink $alternates_path: $OS_ERROR";
533 chdir($orig_cwd) or confess "cd $orig_cwd: $OS_ERROR";
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) };
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.
552 $src =~ s,^(.):/,/$1/,g;
553 print SCRIPT "#!/bin/sh\nexec $src \"\$\@\"\n";
557 sub git_install_hooks
561 my $hooks = $script_path.'/qtrepotools/git-hooks';
562 return if (!-d $hooks);
564 my @configresult = qx(git config --list --local);
565 foreach my $line (@configresult) {
566 next if ($line !~ /submodule\.([^.=]+)\.url=/);
568 my $module_gitdir = $module.'/.git';
569 if (!-d $module_gitdir) {
570 open GITD, $module_gitdir or die "Cannot open $module: $!\n";
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') {
579 $module_gitdir .= '/'.$cd;
580 $module_gitdir = abs_path($module_gitdir);
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');
593 $self->check_if_already_initialized;
595 chomp(my $url = `git config remote.origin.url`);
596 die("Have no origin remote.\n") if (!$url);
599 $self->{'base-url'} = $url;
601 $self->git_clone_all_submodules('qt/qt5', $self->{branch}, @{$self->{'module-subset'}});
603 $self->git_add_remotes('qt/qt5');
605 $self->git_install_hooks;
610 #==============================================================================
612 Qt::InitRepository->new()->run if (!caller);