aboutsummaryrefslogtreecommitdiffstats
path: root/init-repository.pl
diff options
context:
space:
mode:
Diffstat (limited to 'init-repository.pl')
-rwxr-xr-xinit-repository.pl653
1 files changed, 653 insertions, 0 deletions
diff --git a/init-repository.pl b/init-repository.pl
new file mode 100755
index 00000000..3a5f26f6
--- /dev/null
+++ b/init-repository.pl
@@ -0,0 +1,653 @@
+#!/usr/bin/env perl
+# Copyright (C) 2015 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+use v5.8;
+use strict;
+use warnings;
+
+package Qt::InitRepository;
+
+
+sub printUsage($)
+{
+ my ($ex) = @_;
+
+ print <<EOF ;
+Usage:
+ ./init-repository [options]
+
+ This script may be run after an initial `git clone' of Qt5 in order to
+ check out all submodules. It fetches them from canonical URLs inferred
+ from the clone's origin.
+
+Options:
+ Global options:
+
+ --force, -f
+ Force initialization (even if the submodules are already checked
+ out).
+
+ --force-hooks
+ Force initialization of hooks (even if there are already hooks in
+ checked out submodules).
+
+ --quiet, -q
+ Be quiet. Will exit cleanly if the repository is already
+ initialized.
+
+ Module options:
+
+ --module-subset=<module1>,<module2>...
+ Only initialize the specified subset of modules given as the
+ argument. Specified modules must already exist in .gitmodules. The
+ string "all" results in cloning all known modules. The strings
+ "essential", "addon", "preview", "deprecated", "obsolete",
+ "additionalLibrary", and "ignore" refer to classes of modules
+ identified by "status=" lines in the .gitmodules file.
+ You can use "default" in the subset as a short-hand for
+ "essential,addon,preview,deprecated", which corresponds to the set of
+ maintained modules included in standard Qt releases; this is also the
+ default module subset when this option is not given. Entries
+ may be prefixed with a dash to exclude them from a bigger
+ set, e.g. "all,-ignore".
+
+ --no-update
+ Skip the `git submodule update' command.
+
+ --no-fetch
+ Skip the `git fetch' commands. Implied by --no-update.
+
+ --branch
+ Instead of checking out specific SHA1s, check out the submodule
+ branches that correspond with the current supermodule commit. By
+ default, this option will cause local commits in the submodules to
+ be rebased. With --no-update, the branches will be checked out, but
+ their heads will not move.
+
+ --ignore-submodules
+ Set git config to ignore submodules by default when doing operations
+ on the qt5 repo, such as `pull', `fetch', `diff' etc.
+
+ After using this option, pass `--ignore-submodules=none' to git to
+ override it as needed.
+
+ Repository options:
+
+ --berlin
+ Switch to internal URLs and make use of the Berlin git mirrors.
+ (Implies `--mirror').
+
+ --oslo
+ Switch to internal URLs and make use of the Oslo git mirrors.
+ (Implies `--mirror').
+
+ --codereview-username <Gerrit/JIRA username>
+ Specify the user name for the (potentially) writable `gerrit' remote
+ for each module, for use with the Gerrit code review tool.
+
+ If this option is omitted, the gerrit remote is created without a
+ username and port number, and thus relies on a correct SSH
+ configuration.
+
+ --alternates <path to other Qt5 repo>
+ Adds alternates for each submodule to another full qt5 checkout.
+ This makes this qt5 checkout very small, as it will use the object
+ store of the alternates before unique objects are stored in its own
+ object store.
+
+ This option has no effect when using `--no-update'.
+
+ NOTE: This will make this repo dependent on the alternate, which is
+ potentially dangerous! The dependency can be broken by also using
+ the `--copy-objects' option, or by running "git repack -a" in each
+ submodule, where required. Please read the note about the `--shared'
+ option in the documentation of `git clone' for more information.
+
+ --copy-objects
+ When `--alternates' is used, automatically do a "git repack -a" in
+ each submodule after cloning, to ensure that the repositories are
+ independent from the source used as a reference for cloning.
+
+ Note that this negates the disk usage benefits gained from the use
+ of `--alternates'.
+
+ --mirror <url-base>
+ Uses <url-base> as the base URL for submodule git mirrors.
+
+ For example:
+
+ --mirror user\@machine:/foo/bar/qt/
+
+ ...will use the following as a mirror for qtbase:
+
+ user\@machine:/foo/bar/qt/qtbase.git
+
+ The mirror is permitted to contain a subset of the submodules; any
+ missing modules will fall back to the canonical URLs.
+
+EOF
+ exit($ex);
+}
+
+use Carp qw( confess );
+use Cwd qw( getcwd abs_path );
+use English qw( -no_match_vars );
+use File::Spec::Functions qw ( rel2abs );
+use Getopt::Long qw( GetOptions );
+
+my $script_path = abs_path($0);
+$script_path =~ s,[/\\][^/\\]+$,,;
+
+my $GERRIT_SSH_BASE
+ = 'ssh://@USER@codereview.qt-project.org@PORT@/qt/';
+
+my $BER_MIRROR_URL_BASE
+ = 'git://hegel/qt/';
+
+my $OSLO_MIRROR_URL_BASE
+ = 'git://qilin/qt/';
+
+sub new
+{
+ my ($class, @arguments) = @_;
+
+ my $self = {};
+ bless $self, $class;
+ $self->parse_arguments(@arguments);
+
+ return $self;
+}
+
+# Like `system', but possibly log the command, and die on non-zero exit code
+sub exe
+{
+ my ($self, @cmd) = @_;
+
+ if (!$self->{quiet}) {
+ print "+ @cmd\n";
+ }
+
+ if (system(@cmd) != 0) {
+ confess "@cmd exited with status $CHILD_ERROR";
+ }
+
+ return;
+}
+
+sub parse_arguments
+{
+ my ($self) = @_;
+
+ %{$self} = (%{$self},
+ 'alternates' => "",
+ 'branch' => 0,
+ 'codereview-username' => "",
+ 'detach-alternates' => 0 ,
+ 'force' => 0 ,
+ 'force-hooks' => 0 ,
+ 'ignore-submodules' => 0 ,
+ 'mirror-url' => "",
+ 'update' => 1 ,
+ 'fetch' => 1 ,
+ 'module-subset' => "default",
+ );
+
+ GetOptions(
+ 'alternates=s' => \$self->{qw{ alternates }},
+ 'branch' => \$self->{qw{ branch }},
+ 'codereview-username=s' => \$self->{qw{ codereview-username }},
+ 'copy-objects' => \$self->{qw{ detach-alternates }},
+ 'force|f' => \$self->{qw{ force }},
+ 'force-hooks' => \$self->{qw{ force-hooks }},
+ 'ignore-submodules' => \$self->{qw{ ignore-submodules }},
+ 'mirror=s' => \$self->{qw{ mirror-url }},
+ 'quiet' => \$self->{qw{ quiet }},
+ 'update!' => \$self->{qw{ update }},
+ 'fetch!' => \$self->{qw{ fetch }},
+ 'module-subset=s' => \$self->{qw{ module-subset }},
+
+ 'help|?' => sub { printUsage(1); },
+
+ 'berlin' => sub {
+ $self->{'mirror-url'} = $BER_MIRROR_URL_BASE;
+ },
+ 'oslo' => sub {
+ $self->{'mirror-url'} = $OSLO_MIRROR_URL_BASE;
+ },
+ ) || printUsage(2);
+ @ARGV && printUsage(2);
+
+ # Replace any double trailing slashes from end of mirror
+ $self->{'mirror-url'} =~ s{//+$}{/};
+
+ $self->{'module-subset'} =~ s/\bdefault\b/preview,essential,addon,deprecated/;
+ $self->{'module-subset'} = [ split(/,/, $self->{'module-subset'}) ];
+
+ $self->{'fetch'} = 0 if (!$self->{'update'});
+
+ return;
+}
+
+sub check_if_already_initialized
+{
+ my ($self) = @_;
+
+ # We consider the repo as `initialized' if submodule.qtbase.url is set
+ if (qx(git config --get submodule.qtbase.url)) {
+ if (!$self->{force}) {
+ exit 0 if ($self->{quiet});
+ print "Will not reinitialize already initialized repository (use -f to force)!\n";
+ exit 1;
+ }
+ }
+
+ return;
+}
+
+sub git_submodule_init
+{
+ my ($self, @init_args) = @_;
+
+ if ($self->{quiet}) {
+ unshift @init_args, '--quiet';
+ }
+ $self->exe('git', 'submodule', 'init', @init_args);
+
+ my $template = getcwd()."/.commit-template";
+ if (-e $template) {
+ $self->exe('git', 'config', 'commit.template', $template);
+ }
+
+ return;
+}
+
+use constant {
+ STS_PREVIEW => 1,
+ STS_ESSENTIAL => 2,
+ STS_ADDON => 3,
+ STS_DEPRECATED => 4,
+ STS_OBSOLETE => 5,
+ STS_ADDITIONAL => 6
+};
+
+sub has_url_scheme
+{
+ my ($url) = @_;
+ return $url =~ "^[a-z][a-z0-9+\-.]*://";
+}
+
+sub git_clone_all_submodules
+{
+ my ($self, $my_repo_base, $co_branch, $alternates, @subset) = @_;
+
+ my %subdirs = ();
+ my %subbranches = ();
+ my %subbases = ();
+ my %subinits = ();
+ my @submodconfig = qx(git config -l -f .gitmodules);
+ foreach my $line (@submodconfig) {
+ # Example line: submodule.qtqa.url=../qtqa.git
+ next if ($line !~ /^submodule\.([^.=]+)\.([^.=]+)=(.*)$/);
+ if ($2 eq "path") {
+ $subdirs{$1} = $3;
+ } elsif ($2 eq "branch") {
+ $subbranches{$1} = $3;
+ } elsif ($2 eq "url") {
+ my ($mod, $base) = ($1, $3);
+ if (!has_url_scheme($base)) {
+ $base = $my_repo_base.'/'.$base;
+ }
+ while ($base =~ s,(?!\.\./)[^/]+/\.\./,,g) {}
+ $subbases{$mod} = $base;
+ } elsif ($2 eq "update") {
+ push @subset, '-'.$1 if ($3 eq 'none');
+ } elsif ($2 eq "status") {
+ if ($3 eq "preview") {
+ $subinits{$1} = STS_PREVIEW;
+ } elsif ($3 eq "essential") {
+ $subinits{$1} = STS_ESSENTIAL;
+ } elsif ($3 eq "addon") {
+ $subinits{$1} = STS_ADDON;
+ } elsif ($3 eq "deprecated") {
+ $subinits{$1} = STS_DEPRECATED;
+ } elsif ($3 eq "obsolete") {
+ $subinits{$1} = STS_OBSOLETE;
+ } elsif ($3 eq "additionalLibrary") {
+ $subinits{$1} = STS_ADDITIONAL;
+ } elsif ($3 eq "ignore") {
+ delete $subinits{$1};
+ } else {
+ die("Invalid subrepo status '$3' for '$1'.\n");
+ }
+ }
+ }
+
+ my %include = ();
+ foreach my $mod (@subset) {
+ my $del = ($mod =~ s/^-//);
+ my $fail = 0;
+ my @what;
+ if ($mod eq "all") {
+ @what = keys %subbases;
+ } elsif ($mod eq "essential") {
+ @what = grep { ($subinits{$_} || 0) eq STS_ESSENTIAL } keys %subbases;
+ } elsif ($mod eq "addon") {
+ @what = grep { ($subinits{$_} || 0) eq STS_ADDON } keys %subbases;
+ } elsif ($mod eq "additionalLibrary") {
+ @what = grep { ($subinits{$_} || 0) eq STS_ADDITIONAL } keys %subbases;
+ } elsif ($mod eq "preview") {
+ @what = grep { ($subinits{$_} || 0) eq STS_PREVIEW } keys %subbases;
+ } elsif ($mod eq "deprecated") {
+ @what = grep { ($subinits{$_} || 0) eq STS_DEPRECATED } keys %subbases;
+ } elsif ($mod eq "obsolete") {
+ @what = grep { ($subinits{$_} || 0) eq STS_OBSOLETE } keys %subbases;
+ } elsif ($mod eq "ignore") {
+ @what = grep { ($subinits{$_} || 0) eq 0 } keys %subbases;
+ } elsif (defined($subdirs{$mod})) {
+ push @what, $mod;
+ } else {
+ $fail = 1;
+ }
+ if ($del) {
+ print "Warning: excluding non-existent module '$mod'.\n"
+ if ($fail);
+ map { delete $include{$_} } @what;
+ } else {
+ die("Error: module subset names non-existent '$mod'.\n")
+ if ($fail);
+ map { $include{$_} = 1; } @what;
+ }
+ }
+
+ my @modules = sort keys %include;
+
+ $self->git_submodule_init(map { $subdirs{$_} } @modules);
+
+ # manually clone each repo here, so we can easily use reference repos, mirrors etc
+ my @configresult = qx(git config -l);
+ foreach my $line (@configresult) {
+ # Example line: submodule.qtqa.url=git://code.qt.io/qt/qtqa.git
+ next if ($line !~ /submodule\.([^.=]+)\.url=/);
+ my $module = $1;
+
+ if (!defined($include{$module})) {
+ $self->exe('git', 'config', '--remove-section', "submodule.$module");
+ next;
+ }
+
+ if ($self->{'ignore-submodules'}) {
+ $self->exe('git', 'config', "submodule.$module.ignore", 'all');
+ }
+ }
+
+ my $any_bad = 0;
+ foreach my $module (@modules) {
+ $any_bad = 1
+ if ($self->git_stat_one_submodule($subdirs{$module}));
+ }
+ die("Dirty submodule(s) present; cannot proceed.\n")
+ if ($any_bad);
+
+ foreach my $module (@modules) {
+ $self->git_clone_one_submodule($subdirs{$module}, $subbases{$module},
+ $co_branch && $subbranches{$module}, $alternates);
+ }
+
+ if ($co_branch) {
+ foreach my $module (@modules) {
+ my $branch = $subbranches{$module};
+ die("No branch defined for submodule $module.\n") if (!defined($branch));
+ my $orig_cwd = getcwd();
+ my $module_dir = $subdirs{$module};
+ chdir($module_dir) or confess "chdir $module_dir: $OS_ERROR";
+ my $br = qx(git rev-parse -q --verify $branch);
+ if (!$br) {
+ $self->exe('git', 'checkout', '-b', $branch, "origin/$branch");
+ } else {
+ $self->exe('git', 'checkout', $branch);
+ }
+ chdir("$orig_cwd") or confess "chdir $orig_cwd: $OS_ERROR";
+ }
+ }
+ if ($self->{update}) {
+ my @cmd = ('git', 'submodule', 'update', '--force', '--no-fetch');
+ push @cmd, '--remote', '--rebase' if ($co_branch);
+ $self->exe(@cmd);
+
+ foreach my $module (@modules) {
+ if (-f $module.'/.gitmodules') {
+ my $orig_cwd = getcwd();
+ chdir($module) or confess "chdir $module: $OS_ERROR";
+ $self->git_clone_all_submodules($subbases{$module}, 0, "$alternates/$module", "all");
+ chdir("$orig_cwd") or confess "chdir $orig_cwd: $OS_ERROR";
+ }
+ }
+ }
+
+ return;
+}
+
+sub git_add_remotes
+{
+ my ($self, $gerrit_repo_basename) = @_;
+
+ my $gerrit_repo_url = $GERRIT_SSH_BASE;
+ # If given a username, make a "verbose" remote.
+ # Otherwise, rely on proper SSH configuration.
+ if ($self->{'codereview-username'}) {
+ $gerrit_repo_url =~ s,\@USER\@,$self->{'codereview-username'}\@,;
+ $gerrit_repo_url =~ s,\@PORT\@,:29418,;
+ } else {
+ $gerrit_repo_url =~ s,\@[^\@]+\@,,g;
+ }
+
+ $gerrit_repo_url .= $gerrit_repo_basename;
+ $self->exe('git', 'config', 'remote.gerrit.url', $gerrit_repo_url);
+ $self->exe('git', 'config', 'remote.gerrit.fetch', '+refs/heads/*:refs/remotes/gerrit/*', '/heads/');
+}
+
+sub git_stat_one_submodule
+{
+ my ($self, $submodule) = @_;
+
+ return 0 if (! -e "$submodule/.git");
+
+ my $orig_cwd = getcwd();
+ chdir($submodule) or confess "chdir $submodule: $OS_ERROR";
+
+ my @sts = qx(git status --porcelain --untracked=no --ignore-submodules=all);
+
+ # After a git clone --no-checkout, git status reports all files as
+ # staged for deletion, but we still want to update the submodule.
+ # It's unlikely that a genuinely dirty index would have _only_ this
+ # type of modifications, and it doesn't seem like a horribly big deal
+ # to lose them anyway, so ignore them.
+ @sts = grep(!/^D /, @sts);
+
+ chdir($orig_cwd) or confess "cd $orig_cwd: $OS_ERROR";
+
+ return 0 if (!@sts);
+
+ print STDERR "$submodule is dirty.\n";
+
+ return -1;
+}
+
+sub git_clone_one_submodule
+{
+ my ($self, $submodule, $repo_basename, $branch, $alternates) = @_;
+
+ my $mirror_url = $self->{ 'mirror-url' };
+ my $protocol = $self->{ 'protocol' };
+
+ # `--reference FOO' args for the clone, if any.
+ my @reference_args;
+
+ if ($alternates) {
+ # alternates is a qt5 repo, so the submodule will be under that.
+ if (-e "$alternates/$submodule/.git") {
+ @reference_args = ('--reference', "$alternates/$submodule");
+ }
+ else {
+ print " *** $alternates/$submodule not found, ignoring alternate for this submodule\n";
+ }
+ }
+
+ my $do_clone = (! -e "$submodule/.git");
+
+ my $url = $repo_basename;
+ if (!has_url_scheme($url)) {
+ $url = $self->{'base-url'}.$url;
+ }
+
+ my $mirror;
+ if (!has_url_scheme($repo_basename) && $mirror_url && ($do_clone || $self->{fetch})) {
+ $mirror = $mirror_url.$repo_basename;
+ }
+
+ if ($mirror) {
+ # Only use the mirror if it can be reached.
+ eval { $self->exe('git', 'ls-remote', $mirror, 'test/if/mirror/exists') };
+ if ($@) {
+ warn "mirror [$mirror] is not accessible; $url will be used\n";
+ undef $mirror;
+ }
+ }
+
+ if ($do_clone) {
+ if ($branch) {
+ push @reference_args, '--branch', $branch;
+ } else {
+ push @reference_args, '--no-checkout';
+ }
+ $self->exe('git', 'clone', @reference_args,
+ ($mirror ? $mirror : $url), $submodule);
+ }
+
+ my $orig_cwd = getcwd();
+ chdir($submodule) or confess "chdir $submodule: $OS_ERROR";
+
+ if ($mirror) {
+ # This is only for the user's convenience - we make no use of it.
+ $self->exe('git', 'config', 'remote.mirror.url', $mirror);
+ $self->exe('git', 'config', 'remote.mirror.fetch', '+refs/heads/*:refs/remotes/mirror/*');
+ }
+
+ if (!$do_clone && $self->{fetch}) {
+ # If we didn't clone, fetch from the right location. We always update
+ # the origin remote, so that submodule update --remote works.
+ $self->exe('git', 'config', 'remote.origin.url', ($mirror ? $mirror : $url));
+ $self->exe('git', 'fetch', 'origin');
+ }
+
+ if (!($do_clone || $self->{fetch}) || $mirror) {
+ # Leave the origin configured to the canonical URL. It's already correct
+ # if we cloned/fetched without a mirror; otherwise it may be anything.
+ $self->exe('git', 'config', 'remote.origin.url', $url);
+ }
+
+ my $template = $orig_cwd."/.commit-template";
+ if (-e $template) {
+ $self->exe('git', 'config', 'commit.template', $template);
+ }
+
+ if (!has_url_scheme($repo_basename)) {
+ $self->git_add_remotes($repo_basename);
+ }
+
+ if ($self->{'detach-alternates'}) {
+ $self->exe('git', 'repack', '-a');
+
+ my $alternates_path = '.git/objects/info/alternates';
+ if (-e $alternates_path) {
+ unlink($alternates_path) || confess "unlink $alternates_path: $OS_ERROR";
+ }
+ }
+
+ chdir($orig_cwd) or confess "cd $orig_cwd: $OS_ERROR";
+
+ return;
+}
+
+sub ensure_link
+{
+ my ($self, $src, $tgt) = @_;
+ return if (!$self->{'force-hooks'} and -f $tgt);
+ unlink($tgt); # In case we have a dead symlink or pre-existing hook
+ print "Aliasing $src\n as $tgt ...\n" if (!$self->{quiet});
+ if ($^O ne "msys" && $^O ne "MSWin32") {
+ return if eval { symlink($src, $tgt) };
+ }
+ # Windows doesn't do (proper) symlinks. As the post_commit script needs
+ # them to locate itself, we write a forwarding script instead.
+ open SCRIPT, ">".$tgt or die "Cannot create forwarding script $tgt: $!\n";
+ # Make the path palatable for MSYS.
+ $src =~ s,\\,/,g;
+ $src =~ s,^(.):/,/$1/,g;
+ print SCRIPT "#!/bin/sh\nexec $src \"\$\@\"\n";
+ close SCRIPT;
+}
+
+sub git_install_hooks
+{
+ my ($self) = @_;
+
+ my $hooks = $script_path.'/qtrepotools/git-hooks';
+ if (!-d $hooks) {
+ print "Warning: cannot find Git hooks, qtrepotools module might be absent\n";
+ return;
+ };
+
+ my @configresult = qx(git config --list --local);
+ foreach my $line (@configresult) {
+ next if ($line !~ /submodule\.([^.=]+)\.url=/);
+ my $module = $1;
+ my $module_gitdir = $module.'/.git';
+ if (!-d $module_gitdir) {
+ open GITD, $module_gitdir or die "Cannot open $module: $!\n";
+ my $gd = <GITD>;
+ close GITD;
+ chomp($gd);
+ $gd =~ s/^gitdir: // or die "Malformed .git file $module_gitdir\n";
+ $module_gitdir = rel2abs($gd, $module);
+ if (open COMD, $module_gitdir.'/commondir') {
+ my $cd = <COMD>;
+ chomp($cd);
+ $module_gitdir .= '/'.$cd;
+ $module_gitdir = abs_path($module_gitdir);
+ close COMD;
+ }
+ }
+ $self->ensure_link($hooks.'/gerrit_commit_msg_hook', $module_gitdir.'/hooks/commit-msg');
+ $self->ensure_link($hooks.'/git_post_commit_hook', $module_gitdir.'/hooks/post-commit');
+ $self->ensure_link($hooks.'/clang-format-pre-commit', $module_gitdir.'/hooks/pre-commit');
+ }
+}
+
+sub run
+{
+ my ($self) = @_;
+
+ $self->check_if_already_initialized;
+
+ chomp(my $url = `git config remote.origin.url`);
+ die("Have no origin remote.\n") if (!$url);
+ $url =~ s,\.git/?$,,;
+ $url =~ s/((?:tqtc-)?qt5)$//;
+ my $qtrepo = $1 || 'qt5';
+ $self->{'base-url'} = $url;
+
+ $self->git_clone_all_submodules($qtrepo, $self->{branch}, $self->{alternates}, @{$self->{'module-subset'}});
+
+ $self->git_add_remotes($qtrepo);
+
+ $self->git_install_hooks;
+
+ return;
+}
+
+#==============================================================================
+
+Qt::InitRepository->new()->run if (!caller);
+1;