Fold base.git into ghc.git (re #8545)
[ghc.git] / sync-all
index 7c19892..70c9639 100755 (executable)
--- a/sync-all
+++ b/sync-all
@@ -2,9 +2,13 @@
 
 use strict;
 use Cwd;
+use English;
 
 $| = 1; # autoflush stdout after each print, to avoid output after die
 
+my $initial_working_directory;
+my $exit_via_die;
+
 my $defaultrepo;
 my @packages;
 my $verbose = 2;
@@ -16,76 +20,22 @@ my $bare_flag = ""; # NOT the opposite of checked_out_flag (describes local repo
 
 my %tags;
 
-# Figure out where to get the other repositories from.
-sub getrepo {
-    my $repo;
+my $GITHUB = qr!(?:git@|git://|https://|http://)github.com!;
 
-    if (defined($defaultrepo)) {
-        $repo = $defaultrepo;
-        chomp $repo;
-    } else {
-        # Figure out where to get the other repositories from,
-        # based on where this GHC repo came from.
-        my $git_dir = $bare_flag ? "--git-dir=ghc.git" : "";
-        my $branch  = `git $git_dir branch | grep "\* " | sed "s/^\* //"`; chomp $branch;
-        my $remote  = `git $git_dir config branch.$branch.remote`;         chomp $remote;
-        if ($remote eq "") {
-            # remotes are not mandatory for branches (e.g. not recorded by default for bare repos)
-            $remote = "origin";
-        }
-        $repo       = `git $git_dir config remote.$remote.url`;            chomp $repo;
-    }
-
-    my $repo_base;
-    my $checked_out_tree;
+sub inDir {
+    my $dir = shift;
+    my $code = shift;
 
-    if ($repo =~ /^...*:/) {
-        # HTTP or SSH
-        # Above regex says "at least two chars before the :", to avoid
-        # catching Win32 drives ("C:\").
-        $repo_base = $repo;
+    if ($dir ne '.') {
+        chdir($dir);
+    }
 
-        # --checked-out is needed if you want to use a checked-out repo
-        # over SSH or HTTP
-        if ($checked_out_flag) {
-            $checked_out_tree = 1;
-        } else {
-            $checked_out_tree = 0;
-        }
+    my $result = &$code();
 
-        # Don't drop the last part of the path if specified with -r, as
-        # it expects repos of the form:
-        #
-        #   http://darcs.haskell.org
-        #
-        # rather than
-        #
-        #   http://darcs.haskell.org/ghc
-        #
-        if (!$defaultrepo) {
-            $repo_base =~ s#/[^/]+/?$##;
-        }
-    }
-    elsif ($repo =~ /^\/|\.\.\/|.:(\/|\\)/) {
-        # Local filesystem, either absolute (C:/ or /) or relative (../) path
-        $repo_base = $repo;
-        if (-f "$repo/HEAD") {
-            # assume a local mirror:
-            $checked_out_tree = 0;
-            $repo_base =~ s#/[^/]+/?$##;
-        } elsif (-d "$repo/ghc.git") {
-            # assume a local mirror:
-            $checked_out_tree = 0;
-        } else {
-            # assume a checked-out tree:
-            $checked_out_tree = 1;
-        }
-    }
-    else {
-        die "Couldn't work out repo";
+    if ($dir ne '.') {
+        chdir($initial_working_directory);
     }
-
-    return $repo_base, $checked_out_tree;
+    return $result;
 }
 
 sub parsePackages {
@@ -108,8 +58,10 @@ sub parsePackages {
             $line{"localpath"}  = $1;
             $line{"tag"}        = $2;
             $line{"remotepath"} = $3;
-            $line{"vcs"}        = $4;
+            $line{"upstreamurl"}= $4;
             push @packages, \%line;
+
+            $tags{$2} = 0;
         }
         elsif (! /^(#.*)?$/) {
             die "Bad content on line $lineNum of packages file: $_";
@@ -117,6 +69,16 @@ sub parsePackages {
     }
 }
 
+sub tryReadFile {
+    my $filename = shift;
+    my @lines;
+
+    open (FH, $filename) or return "";
+    @lines = <FH>;
+    close FH;
+    return join('', @lines);
+}
+
 sub message {
     if ($verbose >= 2) {
         print "@_\n";
@@ -133,13 +95,11 @@ sub gitNewWorkdir {
     my $dir = shift;
     my $target = shift;
     my $target_dir = "$target/$dir";
-    my $pwd;
 
     if ($dir eq '.') {
         message "== running git-new-workdir . $target_dir @_";
     } else {
         message "== $dir: running git-new-workdir . $target_dir @_";
-        $pwd = getcwd();
         chdir($dir);
     }
 
@@ -148,58 +108,164 @@ sub gitNewWorkdir {
         or die "git-new-workdir failed: $?";
 
     if ($dir ne '.') {
-        chdir($pwd);
+        chdir($initial_working_directory);
     }
 }
 
-sub scm {
+sub git {
     my $dir = shift;
-    my $scm = shift;
-    my $pwd;
+    my @args = @_;
 
-    if ($dir eq '.') {
-        message "== running $scm @_";
+    &inDir($dir, sub {
+        my $prefix = $dir eq '.' ? "" : "$dir: ";
+        message "== ${prefix}running git @args";
+
+        system ("git", @args) == 0
+            or $ignore_failure
+            or die "git failed: $?";
+    });
+}
+
+sub readgitline {
+    my $dir = shift;
+    my @args = @_;
+
+    &inDir($dir, sub {
+        open my $fh, '-|', 'git', @args
+            or die "Executing git @args failed: $!";
+        my $line = <$fh>;
+        $line = "" unless defined($line);
+        chomp $line;
+        close $fh;
+        return $line;
+    });
+}
+
+sub readgit {
+    my $dir = shift;
+    my @args = @_;
+
+    &inDir($dir, sub {
+        open my $fh, '-|', 'git', @args
+            or die "Executing git @args failed: $!";
+        my $ret;
+        $ret .= $_ while <$fh>;
+        close $fh;
+        return $ret;
+    });
+}
+
+sub configure_repository {
+    my $localpath = shift;
+
+    &git($localpath, "config", "--local", "core.ignorecase", "true");
+
+    my $autocrlf = &readgitline($localpath, 'config', '--get', 'core.autocrlf');
+    if ($autocrlf eq "true") {
+        &git($localpath, "config", "--local", "core.autocrlf", "false");
+        &git($localpath, "reset", "--hard");
+    }
+}
+
+# Figure out where to get the other repositories from.
+sub getrepo {
+    my $repo;
+
+    if (defined($defaultrepo)) {
+        $repo = $defaultrepo;
+        chomp $repo;
     } else {
-        message "== $dir: running $scm @_";
-        $pwd = getcwd();
-        chdir($dir);
+        # Figure out where to get the other repositories from,
+        # based on where this GHC repo came from.
+        my $git_dir = $bare_flag ? "ghc.git" : ".";
+        my $branch = &readgitline($git_dir, "rev-parse", "--abbrev-ref", "HEAD");
+        die "Bad branch: $branch"
+            unless $branch =~ m!^[a-zA-Z][a-zA-Z0-9./-]*$!;
+        my $remote = &readgitline($git_dir, "config", "branch.$branch.remote");
+        if ($remote eq "") {
+            # remotes are not mandatory for branches (e.g. not recorded by default for bare repos)
+            $remote = "origin";
+        }
+        die "Bad remote: $remote"
+            unless $remote =~ m!^[a-zA-Z][a-zA-Z0-9./-]*$!;
+        $repo = &readgitline($git_dir, "config", "remote.$remote.url");
     }
 
-    system ($scm, @_) == 0
-        or $ignore_failure
-        or die "$scm failed: $?";
+    my $repo_base;
+    my $checked_out_tree;
+    my $repo_local = 0;
 
-    if ($dir ne '.') {
-        chdir($pwd);
+    if ($repo =~ /^...*:/) {
+        # HTTP or SSH
+        # Above regex says "at least two chars before the :", to avoid
+        # catching Win32 drives ("C:\").
+        $repo_base = $repo;
+
+        # --checked-out is needed if you want to use a checked-out repo
+        # over SSH or HTTP
+        if ($checked_out_flag) {
+            $checked_out_tree = 1;
+        } else {
+            $checked_out_tree = 0;
+        }
+
+        # Don't drop the last part of the path if specified with -r, as
+        # it expects repos of the form:
+        #
+        #   http://git.haskell.org
+        #
+        # rather than
+        #
+        #   http://git.haskell.org/ghc
+        #
+        if (!$defaultrepo) {
+            $repo_base =~ s#/[^/]+/?$##;
+        }
     }
+    elsif ($repo =~ /^\/|\.\.\/|.:(\/|\\)/) {
+        # Local filesystem, either absolute (C:/ or /) or relative (../) path
+        $repo_local = 1;
+        $repo_base = $repo;
+        if (-f "$repo/HEAD") {
+            # assume a local mirror:
+            $checked_out_tree = 0;
+            $repo_base =~ s#/[^/]+/?$##;
+        } elsif (-d "$repo/ghc.git") {
+            # assume a local mirror:
+            $checked_out_tree = 0;
+        } else {
+            # assume a checked-out tree:
+            $checked_out_tree = 1;
+        }
+    }
+    else {
+        die "Couldn't work out repo";
+    }
+
+    return $repo_base, $checked_out_tree, $repo_local;
 }
 
-sub scmall {
+sub gitall {
     my $command = shift;
 
     my $localpath;
     my $tag;
     my $remotepath;
-    my $scm;
     my $line;
     my $branch_name;
     my $subcommand;
 
     my $path;
-    my $wd_before = getcwd;
 
-    my $pwd;
     my @args;
 
     my $started;
     my $doing;
     my $start_repo;
 
-    my ($repo_base, $checked_out_tree) = getrepo();
-
-    my $is_github_repo = $repo_base =~ m/(git@|git:\/\/|https:\/\/)github.com/;
+    my ($repo_base, $checked_out_tree, $repo_local) = getrepo();
 
-    parsePackages;
+    my $is_github_repo = $repo_base =~ $GITHUB;
 
     @args = ();
 
@@ -209,7 +275,10 @@ sub scmall {
         }
         if (@_ < 1) { help(1); }
         $subcommand = shift;
-        if ($subcommand ne 'add' && $subcommand ne 'rm' && $subcommand ne 'set-url') {
+        if ($subcommand ne 'add' &&
+            $subcommand ne 'rm' &&
+            $subcommand ne 'set-branches' &&
+            $subcommand ne 'set-url') {
             help(1);
         }
         while (@_ > 0 && $_[0] =~ /^-/) {
@@ -252,9 +321,8 @@ sub scmall {
 
     for $line (@packages) {
         $tag        = $$line{"tag"};
-        $scm        = $$line{"vcs"};
         # Use the "remote" structure for bare git repositories
-        $localpath  = ($bare_flag && $scm eq "git") ?
+        $localpath  = ($bare_flag) ?
                       $$line{"remotepath"} : $$line{"localpath"};
         $remotepath = ($checked_out_tree) ?
                       $$line{"localpath"}  : $$line{"remotepath"};
@@ -274,9 +342,6 @@ sub scmall {
         close RESUME;
         rename "resume.tmp", "resume";
 
-        # Check the SCM is OK as early as possible
-        die "Unknown SCM: $scm" if (($scm ne "darcs") and ($scm ne "git"));
-
         # We can't create directories on GitHub, so we translate
         # "packages/foo" into "package-foo".
         if ($is_github_repo) {
@@ -286,7 +351,9 @@ sub scmall {
         # Construct the path for this package in the repo we pulled from
         $path = "$repo_base/$remotepath";
 
-        if ($command =~ /^(?:g|ge|get)$/) {
+        if ($command eq "get") {
+            next if $remotepath eq "-"; # "git submodule init/update" will get this later
+
             # Skip any repositories we have not included the tag for
             if (not defined($tags{$tag})) {
                 $tags{$tag} = 0;
@@ -298,157 +365,212 @@ sub scmall {
             if (-d $localpath) {
                 warning("$localpath already present; omitting")
                     if $localpath ne ".";
-                if ($scm eq "git") {
-                    scm ($localpath, $scm, "config", "core.ignorecase", "true");
-                }
+                &configure_repository($localpath);
                 next;
             }
 
             # Note that we use "." as the path, as $localpath
             # doesn't exist yet.
-            if ($scm eq "darcs") {
-                # The first time round the loop, default the get-mode
-                if (not defined($get_mode)) {
-                    warning("adding --partial, to override use --complete");
-                    $get_mode = "--partial";
-                }
-                scm (".", $scm, "get", $get_mode, $path, $localpath, @args);
-            }
-            else {
-                my @argsWithBare = @args;
-                push @argsWithBare, $bare_flag if $bare_flag;
-                scm (".", $scm, "clone", $path, $localpath, @argsWithBare);
-                scm ($localpath, $scm, "config", "core.ignorecase", "true");
-            }
+            my @argsWithBare = @args;
+            push @argsWithBare, $bare_flag if $bare_flag;
+            &git(".", "clone", $path, $localpath, @argsWithBare);
+            &configure_repository($localpath);
             next;
         }
 
-        my $darcs_repo_present = 1 if -d "$localpath/_darcs";
-        my $git_repo_present = 1 if -d "$localpath/.git" || ($bare_flag && -d "$localpath");
-        if ($darcs_repo_present) {
-            if ($git_repo_present) {
-                die "Found both _darcs and .git in $localpath";
-            }
-            $scm = "darcs";
-        } elsif ($git_repo_present) {
-            $scm = "git";
-        } elsif ($tag eq "") {
-            die "Required repo $localpath is missing";
-        } else {
-             message "== $localpath repo not present; skipping";
-             next;
-        }
-
-        # Work out the arguments we should give to the SCM
-        if ($command =~ /^(?:w|wh|wha|what|whats|whatsn|whatsne|whatsnew|status)$/) {
-            if ($scm eq "darcs") {
-                $command = "whatsnew";
-            }
-            elsif ($scm eq "git") {
-                $command = "status";
+        my $git_repo_present = 1 if -e "$localpath/.git" || ($bare_flag && -d "$localpath");
+        if (not $git_repo_present) {
+            if ($tag eq "") {
+                die "Required repo $localpath is missing";
             }
             else {
-                die "Unknown scm";
+                 message "== $localpath repo not present; skipping";
+                 next;
             }
+        }
 
-            # Hack around 'darcs whatsnew' failing if there are no changes
-            $ignore_failure = 1;
-            scm ($localpath, $scm, $command, @args);
+        # Work out the arguments we should give to the SCM
+        if ($command eq "status") {
+            &git($localpath, $command, @args);
         }
-        elsif ($command =~ /^commit$/) {
+        elsif ($command eq "commit") {
             # git fails if there is nothing to commit, so ignore failures
             $ignore_failure = 1;
-            scm ($localpath, $scm, "commit", @args);
-        }
-        elsif ($command =~ /^(?:pus|push)$/) {
-            scm ($localpath, $scm, "push", @args);
+            &git($localpath, "commit", @args);
         }
-        elsif ($command =~ /^(?:pul|pull)$/) {
-            scm ($localpath, $scm, "pull", @args);
-        }
-        elsif ($command =~ /^(?:new-workdir)$/) {
-            gitNewWorkdir ($localpath, @args);
+        elsif ($command eq "check_submodules") {
+            # If we have a submodule then check whether it is up-to-date
+            if ($remotepath eq "-") {
+                my %remote_heads;
+
+                message "== Checking sub-module $localpath";
+
+                chdir($localpath);
+
+                open my $lsremote, '-|', 'git', 'ls-remote', '--heads', '-q'
+                    or die "Executing ls-remote failed: $!";
+                while (<$lsremote>) {
+                    if (/^([0-9a-f]{40})\s*refs\/heads\//) {
+                        $remote_heads{$1} = 1;
+                    }
+                    else {
+                        die "Bad output from ls-remote: $_";
+                    }
+                }
+                close($lsremote);
+
+                my $myhead = &readgitline('.', 'rev-parse', '--verify', 'HEAD');
+
+                if (not defined($remote_heads{$myhead})) {
+                    die "Sub module $localpath needs to be pushed; see http://ghc.haskell.org/trac/ghc/wiki/Repositories/Upstream";
+                }
+                
+                chdir($initial_working_directory);
+            }
         }
-        elsif ($command =~ /^(?:s|se|sen|send)$/) {
-            if ($scm eq "darcs") {
-                $command = "send";
+        elsif ($command eq "push") {
+            # We don't automatically push to the submodules. If you want
+            # to push to them then you need to use a special command, as
+            # described on
+            # http://ghc.haskell.org/trac/ghc/wiki/Repositories/Upstream
+            if ($remotepath ne "-") {
+                &git($localpath, "push", @args);
             }
-            elsif ($scm eq "git") {
-                $command = "send-email";
+        }
+        elsif ($command eq "pull") {
+            my $realcmd;
+            my @realargs;
+            if ($remotepath eq "-") {
+                # Only fetch for the submodules. "git submodule update"
+                # will take care of making us point to the right commit.
+                $realcmd = "fetch";
+                # we like "sync-all pull --rebase" to work:
+                @realargs = grep(!/--rebase/,@args);
             }
             else {
-                die "Unknown scm";
+                $realcmd = "pull";
+                @realargs = @args;
             }
-            scm ($localpath, $scm, $command, @args);
+            &git($localpath, $realcmd, @realargs);
+        }
+        elsif ($command eq "new-workdir") {
+            gitNewWorkdir ($localpath, @args);
+        }
+        elsif ($command eq "send") {
+            $command = "send-email";
+            &git($localpath, $command, @args);
         }
-        elsif ($command =~ /^fetch$/) {
-            scm ($localpath, $scm, "fetch", @args);
+        elsif ($command eq "fetch") {
+            &git($localpath, "fetch", @args);
         }
-        elsif ($command =~ /^new$/) {
+        elsif ($command eq "new") {
             my @scm_args = ("log", "$branch_name..");
-            scm ($localpath, $scm, @scm_args, @args);
+            &git($localpath, @scm_args, @args);
         }
-        elsif ($command =~ /^log$/) {
-            scm ($localpath, $scm, "log", @args);
+        elsif ($command eq "log") {
+            &git($localpath, "log", @args);
         }
-        elsif ($command =~ /^remote$/) {
+        elsif ($command eq "remote") {
             my @scm_args;
+            my $rpath;
             $ignore_failure = 1;
+            if ($remotepath eq '-') {
+                $rpath = "$localpath.git"; # N.B.: $localpath lacks the .git suffix
+                if ($localpath =~ /^libraries\//) {
+                    # FIXME: This is just a simple heuristic to
+                    # infer the remotepath for Git submodules. A
+                    # proper solution would require to parse the
+                    # .gitmodules file to obtain the actual
+                    # localpath<->remotepath mapping.
+                    $rpath =~ s/^libraries\//packages\//;
+                }
+                $rpath = "$repo_base/$rpath";
+            } else {
+                $rpath = $path;
+            }
             if ($subcommand eq 'add') {
-                @scm_args = ("remote", "add", $branch_name, $path);
+                @scm_args = ("remote", "add", $branch_name, $rpath);
             } elsif ($subcommand eq 'rm') {
                 @scm_args = ("remote", "rm", $branch_name);
+            } elsif ($subcommand eq 'set-branches') {
+                @scm_args = ("remote", "set-branches", $branch_name);
             } elsif ($subcommand eq 'set-url') {
-                @scm_args = ("remote", "set-url", $branch_name, $path);
+                @scm_args = ("remote", "set-url", $branch_name, $rpath);
             }
-            scm ($localpath, $scm, @scm_args, @args);
+            &git($localpath, @scm_args, @args);
         }
-        elsif ($command =~ /^checkout$/) {
+        elsif ($command eq "checkout") {
             # Not all repos are necessarily branched, so ignore failure
             $ignore_failure = 1;
-            scm ($localpath, $scm, "checkout", @args)
-                unless $scm eq "darcs";
+            &git($localpath, "checkout", @args);
         }
-        elsif ($command =~ /^grep$/) {
+        elsif ($command eq "grep") {
             # Hack around 'git grep' failing if there are no matches
             $ignore_failure = 1;
-            scm ($localpath, $scm, "grep", @args)
-                unless $scm eq "darcs";
+            &git($localpath, "grep", @args);
         }
-        elsif ($command =~ /^diff$/) {
-            scm ($localpath, $scm, "diff", @args)
-                unless $scm eq "darcs";
+        elsif ($command eq "diff") {
+            &git($localpath, "diff", @args);
         }
-        elsif ($command =~ /^clean$/) {
-            scm ($localpath, $scm, "clean", @args)
-                unless $scm eq "darcs";
+        elsif ($command eq "clean") {
+            &git($localpath, "clean", @args);
         }
-        elsif ($command =~ /^reset$/) {
-            scm ($localpath, $scm, "reset", @args)
-                unless $scm eq "darcs";
+        elsif ($command eq "reset") {
+            &git($localpath, "reset", @args);
         }
-        elsif ($command =~ /^branch$/) {
-            scm ($localpath, $scm, "branch", @args)
-                unless $scm eq "darcs";
+        elsif ($command eq "branch") {
+            &git($localpath, "branch", @args);
         }
-        elsif ($command =~ /^config$/) {
-            scm ($localpath, $scm, "config", @args)
-                unless $scm eq "darcs";
+        elsif ($command eq "config") {
+            &git($localpath, "config", @args);
         }
-        elsif ($command =~ /^repack$/) {
-            scm ($localpath, $scm, "repack", @args)
-                if $scm eq "git"
+        elsif ($command eq "repack") {
+            &git($localpath, "repack", @args);
         }
-        elsif ($command =~ /^format-patch$/) {
-            scm ($localpath, $scm, "format-patch", @args)
-                if $scm eq "git"
+        elsif ($command eq "format-patch") {
+            &git($localpath, "format-patch", @args);
         }
-        elsif ($command =~ /^gc$/) {
-            scm ($localpath, $scm, "gc", @args)
-                unless $scm eq "darcs";
+        elsif ($command eq "gc") {
+            &git($localpath, "gc", @args);
         }
-        elsif ($command =~ /^tag$/) {
-            scm ($localpath, $scm, "tag", @args);
+        elsif ($command eq "tag") {
+            &git($localpath, "tag", @args);
+        }
+        elsif ($command eq "compare") {
+            # Don't compare the subrepos; it doesn't work properly as
+            # they aren't on a branch.
+            next if $remotepath eq "-";
+
+            my $compareto;
+            if ($#args eq -1) {
+                $compareto = $path;
+            }
+            elsif ($#args eq 0) {
+                $compareto = "$args[0]/$localpath";
+            }
+            elsif ($#args eq 1 && $args[0] eq "-b") {
+                $compareto = "$args[1]/$remotepath";
+            }
+            else {
+                die "Bad args for compare";
+            }
+            print "$localpath";
+            print (' ' x (40 - length($localpath)));
+            my $branch = &readgitline($localpath, "rev-parse", "--abbrev-ref", "HEAD");
+            die "Bad branch: $branch"
+                unless $branch =~ m!^[a-zA-Z][a-zA-Z0-9./-]*$!;
+            my $us   = &readgitline(".", "ls-remote", $localpath, "refs/heads/$branch");
+            my $them = &readgitline(".", "ls-remote", $compareto, "refs/heads/$branch");
+            $us   =~ s/[[:space:]].*//;
+            $them =~ s/[[:space:]].*//;
+            die "Bad commit of mine: $us"     unless (length($us)   eq 40);
+            die "Bad commit of theirs: $them" unless (length($them) eq 40);
+            if ($us eq $them) {
+                print "same\n";
+            }
+            else {
+                print "DIFFERENT\n";
+            }
         }
         else {
             die "Unknown command: $command";
@@ -458,24 +580,62 @@ sub scmall {
     unlink "resume";
 }
 
+sub gitInitSubmodules {
+    &git(".", "submodule", "init", @_);
+
+    my ($repo_base, $checked_out_tree, $repo_local) = getrepo();
+
+    my $submodulespaths = &readgit(".", "config", "--get-regexp", "^submodule[.].*[.]url");
+    # if we came from github, change the urls appropriately
+    while ($submodulespaths =~ m!^(submodule.libraries/[a-zA-Z0-9]+.url) ($GITHUB)/ghc/packages/([a-zA-Z0-9]+).git$!gm) {
+        &git(".", "config", $1, "$2/ghc/packages-$3");
+    }
+
+    # if we came from a local repository, grab our submodules from their
+    # checkouts over there, if they exist.
+    if ($repo_local) {
+        while ($submodulespaths =~ m!^(submodule.(libraries/[a-zA-Z0-9]+).url) .*$!gm) {
+            if (-e "$repo_base/$2/.git") {
+                &git(".", "config", $1, "$repo_base/$2");
+            }
+        }
+    }
+}
+
+sub checkCurrentBranchIsMaster {
+    my $branch = `git symbolic-ref HEAD`;
+    $branch =~ s/refs\/heads\///;
+    $branch =~ s/\n//;
+
+    if ($branch !~ /master/) {
+        print "\nWarning: You are trying to 'pull' while on branch '$branch'.\n"
+            . "Updates to this script will happen on the master branch which\n"
+            . "means the version on this branch may be out of date.\n\n";
+    }
+}
+
 sub help
 {
         my $exit = shift;
 
+        my $tags = join ' ', sort (grep !/^-$/, keys %tags);
+
         # Get the built in help
         my $help = <<END;
 Usage:
 
 ./sync-all [-q] [-s] [--ignore-failure] [-r repo] [--checked-out] [--bare]
-           [--nofib] [--extra] [--testsuite] [--no-dph] [--resume]
+           [--<tag>] [--no-<tag>] [--resume]
            cmd [git flags]
 
+    where <tag> is one of: $tags
+
 Applies the command "cmd" to each repository in the tree.
 
 A full repository tree is obtained by first cloning the ghc
 repository, then getting the subrepositories with "sync-all get":
 
-  \$ git clone http://darcs.haskell.org/ghc.git
+  \$ git clone http://git.haskell.org/ghc.git
   \$ cd ghc
   \$ ./sync-all get
 
@@ -502,9 +662,9 @@ get
     before "get" that enable extra repositories. The full list is
     given at the end of this help. For example:
 
-    ./sync-all --testsuite get
+    ./sync-all --nofib get
 
-    would get the testsuite repository in addition to the usual set of
+    would get the nofib repository in addition to the usual set of
     subrepositories.
 
 remote add <remote-name>
@@ -515,13 +675,24 @@ remote set-url [--push] <remote-name>
     repository location in each case appropriately. For example, to
     add a new remote pointing to the upstream repositories:
 
-    ./sync-all -r http://darcs.haskell.org/ remote add upstream
+    ./sync-all -r http://git.haskell.org remote add upstream
 
     The -r flag points to the root of the repository tree (see "which
     repos to use" below). For a repository on the local filesystem it
     would point to the ghc repository, and for a remote repository it
     points to the directory containing "ghc.git".
 
+compare
+compare reporoot
+compare -b reporoot
+
+    Compare the git HEADs of the repos to the origin repos, or the
+    repos under reporoot (which is assumde to be a checked-out tree
+    unless the -b flag is used).
+
+    1 line is printed for each repo, indicating whether the repo is
+    at the "same" or a "DIFFERENT" commit.
+
 These commands just run the equivalent git command on each repository, passing
 any extra arguments to git:
 
@@ -578,11 +749,9 @@ Flags given *after* the command are passed to git.
 
   --nofib also clones the nofib benchmark suite
 
-  --testsuite also clones the ghc testsuite 
-
   --extra also clone some extra library packages
 
-  --no-dph avoids cloning the dph pacakges
+  --no-dph avoids cloning the dph packages
 
 
 ------------ Checking out a branch -------------
@@ -612,7 +781,7 @@ otherwise sync-all works on repos of form:
   <repo_base>/<remote-path>
 
 This logic lets you say
-  both    sync-all -r http://darcs.haskell.org/ghc-6.12 remote add ghc-6.12
+  both    sync-all -r http://example.org/ghc-6.12 remote add ghc-6.12
   and     sync-all -r ../working remote add working
 The latter is called a "checked-out tree".
 
@@ -649,8 +818,13 @@ END
 
 sub main {
 
+    &parsePackages();
+
     $tags{"-"} = 1;
     $tags{"dph"} = 1;
+    if ($OSNAME =~ /^(MSWin32|Cygwin|msys)$/) {
+        $tags{"windows"} = 1;
+    }
 
     while ($#_ ne -1) {
         my $arg = shift;
@@ -689,12 +863,15 @@ sub main {
         }
         # --<tag> says we grab the libs tagged 'tag' with
         # 'get'. It has no effect on the other commands.
-        elsif ($arg =~ m/^--no-(.*)$/) {
+        elsif ($arg =~ m/^--no-(.*)$/ && defined($tags{$1})) {
             $tags{$1} = 0;
         }
-        elsif ($arg =~ m/^--(.*)$/) {
+        elsif ($arg =~ m/^--(.*)$/ && defined($tags{$1})) {
             $tags{$1} = 1;
         }
+        elsif ($arg =~ m/^-/) {
+            die "Unrecognised flag: $arg";
+        }
         else {
             unshift @_, $arg;
             if (grep /^-q$/, @_) {
@@ -711,7 +888,7 @@ sub main {
     if ($bare_flag && ! $bare_found && ! $defaultrepo) {
         die "error: bare repository ghc.git not found.\n"
           . "       Either clone a bare ghc repo first or specify the repo location. E.g.:\n"
-          . "       ./sync-all --bare [--testsuite --nofib --extra] -r http://darcs.haskell.org/ get\n"
+          . "       ./sync-all --bare [--nofib --extra] -r http://git.haskell.org get\n"
     }
     elsif ($bare_found) {
         $bare_flag = "--bare";
@@ -725,13 +902,88 @@ sub main {
     }
     else {
         # Give the command and rest of the arguments to the main loop
-        scmall @_;
+        # We normalise command names here to avoid duplicating the
+        # abbreviations that we allow.
+        my $command = shift;
+
+        if ($command =~ /^(?:g|ge|get)$/) {
+            $command = "get";
+        }
+        elsif ($command =~ /^(?:pus|push)$/) {
+            $command = "push";
+        }
+        elsif ($command =~ /^(?:pul|pull)$/) {
+            $command = "pull";
+        }
+        elsif ($command =~ /^(?:s|se|sen|send)$/) {
+            $command = "send";
+        }
+        elsif ($command =~ /^(?:w|wh|wha|what|whats|whatsn|whatsne|whatsnew|status)$/) {
+            $command = "status";
+        }
+
+        if ($command eq "push") {
+            &gitall("check_submodules", @_);
+        }
+
+        &gitall($command, @_);
+
+        my @submodule_args = grep(/^-q/,@_);
+
+        if ($command eq "get") {
+            &gitInitSubmodules(@submodule_args);
+        }
+
+        if ($command eq "pull") {
+            my $gitConfig = &tryReadFile(".git/config");
+            if ($gitConfig !~ /submodule/) {
+                &gitInitSubmodules(@submodule_args);
+            }
+        }
+        if ($command eq "get" or $command eq "pull") {
+            my $gitConfig = &tryReadFile(".git/config");
+            if ($gitConfig !~ /submodule/) {
+                &gitInitSubmodules(@submodule_args);
+            }
+            &git(".", "submodule", "update", @submodule_args);
+        }
+    }
+}
+
+BEGIN {
+    my %argvHash = map { $_, 1 } @ARGV;
+    if ($argvHash {"pull"}) {
+        checkCurrentBranchIsMaster();
+    }
+    $initial_working_directory = getcwd();
+
+    $SIG{__DIE__} = sub {
+      die @_ if $^S;
+      $exit_via_die = 1;
+    };
+
+    #message "== Checking for left-over testsuite/.git folder";
+    if (-d "testsuite/.git") {
+        print <<EOF;
+============================
+ATTENTION!
+
+You have a left-over testsuite/.git folder in your GHC tree!
+
+Please backup or remove it (e.g. "rm -r testsuite/.git") before
+proceeding as the testsuite Git repository is now tracked as part of
+the ghc Git repository (see #8545 for more details)
+============================
+EOF
+        die "detected obsolete testsuite/.git folder"
     }
 }
 
 END {
+    return if $exit_via_die;
     my $ec = $?;
-    my $pwd = getcwd();
+
+    chdir($initial_working_directory);
 
     message "== Checking for old haddock repo";
     if (-d "utils/haddock/.git") {
@@ -748,7 +1000,7 @@ Please remove it (e.g. "rm -r utils/haddock"), and then run
 ============================
 EOF
         }
-        chdir($pwd);
+        chdir($initial_working_directory);
     }
 
     message "== Checking for old binary repo";
@@ -766,7 +1018,7 @@ Please remove it (e.g. "rm -r libraries/binary"), and then run
 ============================
 EOF
         }
-        chdir($pwd);
+        chdir($initial_working_directory);
     }
 
     message "== Checking for old mtl repo";
@@ -784,7 +1036,7 @@ Please remove it (e.g. "rm -r libraries/mtl"), and then run
 ============================
 EOF
         }
-        chdir($pwd);
+        chdir($initial_working_directory);
     }
 
     message "== Checking for old Cabal repo";
@@ -802,7 +1054,43 @@ Please remove it (e.g. "rm -r libraries/Cabal"), and then run
 ============================
 EOF
         }
-        chdir($pwd);
+        chdir($initial_working_directory);
+    }
+
+    message "== Checking for old time from tarball";
+    if (-d "libraries/time" and ! -e "libraries/time/.git") {
+            print <<EOF;
+============================
+ATTENTION!
+
+You have an old time package in your GHC tree!
+
+Please remove it (e.g. "rm -r libraries/time"), and then run
+"./sync-all get" to get the new repository.
+============================
+EOF
+    }
+
+    message "== Checking for obsolete Git repo URL";
+    my $repo_url = &readgitline(".", 'config', '--get', 'remote.origin.url');
+    if ($repo_url =~ /^http:\/\/darcs.haskell.org/) {
+            print <<EOF;
+============================
+ATTENTION!
+
+You seem to be using obsolete Git repository URLs.
+
+Please run
+
+  ./sync-all -r git://git.haskell.org remote set-url
+
+or (in case port 9418/tcp is filtered by your firewall)
+
+  ./sync-all -r http://git.haskell.org remote set-url
+
+to update your local checkout to use the new Git URLs.
+============================
+EOF
     }
 
     $? = $ec;