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}, $subbranches{$module});
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);
403 $self->exe('git', 'checkout', '-b', $branch, "origin/$branch");
405 $self->exe('git', 'checkout', $branch);
407 chdir("$orig_cwd") or confess "chdir $orig_cwd: $OS_ERROR";
410 if ($self->{update}) {
411 my @cmd = ('git', 'submodule', 'update', '--no-fetch');
412 push @cmd, '--remote', '--rebase' if ($co_branch);
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";
430 my ($self, $gerrit_repo_basename) = @_;
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,;
439 $gerrit_repo_url =~ s,\@[^\@]+\@,,g;
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/');
447 sub git_clone_one_submodule
449 my ($self, $submodule, $repo_basename, $branch) = @_;
451 my $alternates = $self->{ 'alternates' };
452 my $mirror_url = $self->{ 'mirror-url' };
453 my $protocol = $self->{ 'protocol' };
455 # `--reference FOO' args for the clone, if any.
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");
464 print " *** $alternates/$submodule not found, ignoring alternate for this submodule\n";
468 my $url = $self->{'base-url'}.$repo_basename;
471 $mirror = $mirror_url.$repo_basename;
475 # Only use the mirror if it can be reached.
476 eval { $self->exe('git', 'ls-remote', $mirror, 'test/if/mirror/exists') };
478 warn "mirror [$mirror] is not accessible; $url will be used\n";
483 my $do_clone = (! -e "$submodule/.git");
485 push @reference_args, '--branch', $branch if ($branch);
486 $self->exe('git', 'clone', @reference_args,
487 ($mirror ? $mirror : $url), $submodule);
490 my $orig_cwd = getcwd();
491 chdir($submodule) or confess "chdir $submodule: $OS_ERROR";
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/*');
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');
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);
512 my $template = getcwd()."/../.commit-template";
514 $self->exe('git', 'config', 'commit.template', $template);
517 $self->git_add_remotes($repo_basename);
519 if ($self->{'detach-alternates'}) {
520 $self->exe('git', 'repack', '-a');
522 my $alternates_path = '.git/objects/info/alternates';
523 if (-e $alternates_path) {
524 unlink($alternates_path) || confess "unlink $alternates_path: $OS_ERROR";
528 chdir($orig_cwd) or confess "cd $orig_cwd: $OS_ERROR";
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) };
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.
547 $src =~ s,^(.):/,/$1/,g;
548 print SCRIPT "#!/bin/sh\nexec $src \"\$\@\"\n";
552 sub git_install_hooks
556 my $hooks = $script_path.'/qtrepotools/git-hooks';
557 return if (!-d $hooks);
559 my @configresult = qx(git config --list --local);
560 foreach my $line (@configresult) {
561 next if ($line !~ /submodule\.([^.=]+)\.url=/);
563 my $module_gitdir = $module.'/.git';
564 if (!-d $module_gitdir) {
565 open GITD, $module_gitdir or die "Cannot open $module: $!\n";
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') {
574 $module_gitdir .= '/'.$cd;
575 $module_gitdir = abs_path($module_gitdir);
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');
588 $self->check_if_already_initialized;
590 chomp(my $url = `git config remote.origin.url`);
591 die("Have no origin remote.\n") if (!$url);
594 $self->{'base-url'} = $url;
596 $self->git_clone_all_submodules('qt/qt5', $self->{branch}, @{$self->{'module-subset'}});
598 $self->git_add_remotes('qt/qt5');
600 $self->git_install_hooks;
605 #==============================================================================
607 Qt::InitRepository->new()->run if (!caller);