Merge win:/cygdrive/c/ghc/git/dt
[ghc.git] / sync-all
index 44da3d7..741e22b 100755 (executable)
--- a/sync-all
+++ b/sync-all
 use strict;
 use Cwd;
 
-# Usage:
-#
-# ./sync-all [-q] [-s] [--ignore-failure] [-r repo]
-#            [--nofib] [--testsuite] [--checked-out] cmd [git flags]
-#
-# Applies the command "cmd" to each repository in the tree.
-# sync-all will try to do the right thing for both git and darcs repositories.
-#
-# e.g.
-#      ./sync-all -r http://darcs.haskell.org/ghc get
-#          To get any repos which do not exist in the local tree
-#
-#      ./sync-all pull
-#          To pull everything from the default repos
-#
-# -------------- Flags -------------------
-#   -q says to be quite, and -s to be silent.
-#
-#   --ignore-failure says to ignore errors and move on to the next repository
-#
-#   -r repo says to use repo as the location of package repositories
-#
-#   --checked-out says that the remote repo is in checked-out layout, as
-#   opposed to the layout used for the main repo.  By default a repo on
-#   the local filesystem is assumed to be checked-out, and repos accessed
-#   via HTTP or SSH are assumed to be in the main repo layout; use
-#   --checked-out to override the latter.
-#
-#   --nofib, --testsuite also get the nofib and testsuite repos respectively
-#
-# ------------ Which repos to use -------------
-# sync-all uses the following algorithm to decide which remote repos to use
-#
-#  It always computes the remote repos from a single base, $repo_base
-#  How is $repo_base set?  
-#    If you say "-r repo", then that's $repo_base
-#    otherwise $repo_base is set by asking git where the ghc repo came
-#    from, and removing the last component (e.g. /ghc.git/ of /ghc/).
-#
-#  Then sync-all iterates over the package found in the file
-#  ./packages; see that file for a description of the contents.
-# 
-#    If $repo_base looks like a local filesystem path, or if you give
-#    the --checked-out flag, sync-all works on repos of form
-#          $repo_base/<local-path>
-#    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 pull
-#      and     sync-all -r ../HEAD pull
-#    The latter is called a "checked-out tree".
-
-# NB: sync-all *ignores* the defaultrepo of all repos other than the
-# root one.  So the remote repos must be laid out in one of the two
-# formats given by <local-path> and <remote-path> in the file 'packages'.
-
 $| = 1; # autoflush stdout after each print, to avoid output after die
 
+my $initial_working_directory;
+
 my $defaultrepo;
 my @packages;
 my $verbose = 2;
+my $try_to_resume = 0;
 my $ignore_failure = 0;
-my $checked_out_flag = 0;
+my $checked_out_flag = 0; # NOT the opposite of bare_flag (describes remote repo state)
 my $get_mode;
+my $bare_flag = ""; # NOT the opposite of checked_out_flag (describes local repo state)
 
 my %tags;
 
-# Figure out where to get the other repositories from.
-sub getrepo {
-    my $basedir = ".";
-    my $repo;
+sub inDir {
+    my $dir = shift;
+    my $code = shift;
 
-    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 $branch = `git branch | grep "\* " | sed "s/^\* //"`; chomp $branch;
-        my $remote = `git config branch.$branch.remote`;         chomp $remote;
-        $repo = `git config remote.$remote.url`;       chomp $repo;
+    if ($dir ne '.') {
+        chdir($dir);
     }
 
-    my $repo_base;
-    my $checked_out_tree;
-
-    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;
-        }
+    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 or relative path
-        # (assumes a checked-out tree):
-        $repo_base = $repo;
-        $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 {
     my @repos;
     my $lineNum;
 
-    open IN, "< packages" or die "Can't open packages file";
+    open IN, "< packages.conf"
+        or open IN, "< packages" # clashes with packages directory when using --bare
+        or die "Can't open packages file (or packages.conf)";
     @repos = <IN>;
     close IN;
 
@@ -142,12 +49,11 @@ sub parsePackages {
     foreach (@repos) {
         chomp;
         $lineNum++;
-        if (/^([^# ]+) +([^ ]+) +([^ ]+) +([^ ]+)$/) {
+        if (/^([^# ]+) +([^ ]+) +([^ ]+)$/) {
             my %line;
             $line{"localpath"}  = $1;
             $line{"tag"}        = $2;
             $line{"remotepath"} = $3;
-            $line{"vcs"}        = $4;
             push @packages, \%line;
         }
         elsif (! /^(#.*)?$/) {
@@ -156,6 +62,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";
@@ -168,45 +84,162 @@ sub warning {
     }
 }
 
-sub scm {
+sub gitNewWorkdir {
     my $dir = shift;
-    my $scm = shift;
-    my $pwd;
+    my $target = shift;
+    my $target_dir = "$target/$dir";
 
     if ($dir eq '.') {
-        message "== running $scm @_";
+        message "== running git-new-workdir . $target_dir @_";
     } else {
-        message "== $dir: running $scm @_";
-        $pwd = getcwd();
+        message "== $dir: running git-new-workdir . $target_dir @_";
         chdir($dir);
     }
 
-    system ($scm, @_) == 0
+    system ("git-new-workdir", ".", $target_dir, @_) == 0
         or $ignore_failure
-        or die "$scm failed: $?";
+        or die "git-new-workdir failed: $?";
 
     if ($dir ne '.') {
-        chdir($pwd);
+        chdir($initial_working_directory);
     }
 }
 
-sub scmall {
+sub git {
+    my $dir = shift;
+    my @args = @_;
+
+    &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 readgit {
+    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 configure_repository {
+    my $localpath = shift;
+
+    &git($localpath, "config", "--local", "core.ignorecase", "true");
+
+    my $autocrlf = &readgit($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 {
+        # 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 = &readgit($git_dir, "rev-parse", "--abbrev-ref", "HEAD");
+        die "Bad branch: $branch"
+            unless $branch =~ /^[a-zA-Z][a-zA-Z0-9.-]*$/;
+        my $remote = &readgit($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 =~ /^[a-zA-Z][a-zA-Z0-9.-]*$/;
+        $repo = &readgit($git_dir, "config", "remote.$remote.url");
+    }
+
+    my $repo_base;
+    my $checked_out_tree;
+
+    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://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";
+    }
+
+    return $repo_base, $checked_out_tree;
+}
+
+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/;
@@ -219,16 +252,19 @@ sub scmall {
         while (@_ > 0 && $_[0] =~ /^-/) {
             push(@args,shift);
         }
-        if (@_ < 1) { help(); }
+        if (@_ < 1) { help(1); }
         $subcommand = shift;
-        if ($subcommand ne 'add' && $subcommand ne 'rm' && $subcommand ne 'set-url') {
-            help();
+        if ($subcommand ne 'add' &&
+            $subcommand ne 'rm' &&
+            $subcommand ne 'set-branches' &&
+            $subcommand ne 'set-url') {
+            help(1);
         }
         while (@_ > 0 && $_[0] =~ /^-/) {
             push(@args,shift);
         }
         if (($subcommand eq 'add' || $subcommand eq 'rm') && @_ < 1) {
-            help();
+            help(1);
         } elsif (@_ < 1) { # set-url
             $branch_name = 'origin';
         } else {
@@ -244,31 +280,59 @@ sub scmall {
 
     push(@args, @_);
 
-    for $line (@packages) {
+    # $doing is a good enough approximation to what we are doing that
+    # we can use it to check that --resume is resuming the right command
+    $doing = join(" ", ($command, @args));
+    $started = 1;
+    if ($try_to_resume && -f "resume") {
+        my $what;
+        open RESUME, "< resume"
+            or die "Can't open resume file";
+        $start_repo = <RESUME>;
+        chomp $start_repo;
+        $what = <RESUME>;
+        chomp $what;
+        close RESUME;
+        if ($what eq $doing) {
+            $started = 0;
+        }
+    }
 
-        $localpath  = $$line{"localpath"};
+    for $line (@packages) {
         $tag        = $$line{"tag"};
-        $remotepath = $$line{"remotepath"};
-        $scm        = $$line{"vcs"};
+        # Use the "remote" structure for bare git repositories
+        $localpath  = ($bare_flag) ?
+                      $$line{"remotepath"} : $$line{"localpath"};
+        $remotepath = ($checked_out_tree) ?
+                      $$line{"localpath"}  : $$line{"remotepath"};
+
+        if (!$started) {
+            if ($start_repo eq $localpath) {
+                $started = 1;
+            }
+            else {
+                next;
+            }
+        }
 
-        # Check the SCM is OK as early as possible
-        die "Unknown SCM: $scm" if (($scm ne "darcs") and ($scm ne "git"));
+        open RESUME, "> resume.tmp";
+        print RESUME "$localpath\n";
+        print RESUME "$doing\n";
+        close RESUME;
+        rename "resume.tmp", "resume";
 
         # We can't create directories on GitHub, so we translate
-        # "package/foo" into "package-foo".
+        # "packages/foo" into "package-foo".
         if ($is_github_repo) {
             $remotepath =~ s/\//-/;
         }
 
-        # Work out the path for this package in the repo we pulled from
-        if ($checked_out_tree) {
-            $path = "$repo_base/$localpath";
-        }
-        else {
-            $path = "$repo_base/$remotepath";
-        }
+        # Construct the path for this package in the repo we pulled from
+        $path = "$repo_base/$remotepath";
+
+        if ($command eq "get") {
+            next if $remotepath eq "-"; # "git submodule init/update" will get this later
 
-        if ($command =~ /^(?:g|ge|get)$/) {
             # Skip any repositories we have not included the tag for
             if (not defined($tags{$tag})) {
                 $tags{$tag} = 0;
@@ -276,177 +340,397 @@ sub scmall {
             if ($tags{$tag} == 0) {
                 next;
             }
-            
+
             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 {
-                scm (".", $scm, "clone", $path, $localpath, @args);
-                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;
         }
 
-        if (-d "$localpath/_darcs") {
-            if (-d "$localpath/.git") {
-                die "Found both _darcs and .git in $localpath";
-            }
-            $scm = "darcs";
-        } elsif (-d "$localpath/.git") {
-            $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);
-        }
-        elsif ($command =~ /^(?:pul|pull)$/) {
-            scm ($localpath, $scm, "pull", @args);
+            &git($localpath, "commit", @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 = &readgit('.', 'rev-parse', '--verify', 'HEAD');
+
+                if (not defined($remote_heads{$myhead})) {
+                    die "Sub module $localpath needs to be pushed; see http://hackage.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://hackage.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 = "$repo_base/$localpath";
+            } 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 eq "diff") {
+            &git($localpath, "diff", @args);
+        }
+        elsif ($command eq "clean") {
+            &git($localpath, "clean", @args);
+        }
+        elsif ($command eq "reset") {
+            &git($localpath, "reset", @args);
+        }
+        elsif ($command eq "branch") {
+            &git($localpath, "branch", @args);
         }
-        elsif ($command =~ /^clean$/) {
-            scm ($localpath, $scm, "clean", @args)
-                unless $scm eq "darcs";
+        elsif ($command eq "config") {
+            &git($localpath, "config", @args);
         }
-        elsif ($command =~ /^reset$/) {
-            scm ($localpath, $scm, "reset", @args)
-                unless $scm eq "darcs";
+        elsif ($command eq "repack") {
+            &git($localpath, "repack", @args);
         }
-        elsif ($command =~ /^config$/) {
-            scm ($localpath, $scm, "config", @args)
-                unless $scm eq "darcs";
+        elsif ($command eq "format-patch") {
+            &git($localpath, "format-patch", @args);
+        }
+        elsif ($command eq "gc") {
+            &git($localpath, "gc", @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 = &readgit($localpath, "rev-parse", "--abbrev-ref", "HEAD");
+            die "Bad branch: $branch"
+                unless $branch =~ /^[a-zA-Z][a-zA-Z0-9.-]*$/;
+            my $us   = &readgit(".", "ls-remote", $localpath, "refs/heads/$branch");
+            my $them = &readgit(".", "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";
         }
     }
-}
 
+    unlink "resume";
+}
 
-sub help()
+sub help
 {
+        my $exit = shift;
+
         # Get the built in help
         my $help = <<END;
 Usage:
 
-  ./sync-all [-q] [-s] [--ignore-failure] [-r repo] [--checked-out]
-             [--package-tag1] ... [--package-tagN] command
-
-Supported commands:
-
- * whatsnew
- * commit
- * push
- * pull
- * get, with options:
-  * --<package-tag>
-  * --complete
-  * --partial
- * fetch
- * send
- * new
- * remote add <remote-name>
- * remote rm <remote-name>
- * remote set-url [--push] <remote-name>
- * checkout
- * grep
- * clean
- * reset
- * config
- * log
+./sync-all [-q] [-s] [--ignore-failure] [-r repo] [--checked-out] [--bare]
+           [--nofib] [--extra] [--testsuite] [--no-dph] [--resume]
+           cmd [git flags]
+
+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
+  \$ cd ghc
+  \$ ./sync-all get
+
+After this, "./sync-all pull" will pull from the original repository
+tree.
+
+A remote pointing to another local repository tree can be added like
+this:
+
+  \$ ./sync-all -r /path/to/ghc remote add otherlocal
+
+and then we can pull from this other tree with
+
+  \$ ./sync-all pull otherlocal
+
+-------------- Commands -----------------
+get
+
+    Clones all sub-repositories from the same place that the ghc
+    repository was cloned from. See "which repos to use" below
+    for details of how the subrepositories are laid out.
+
+    There are various --<package-tag> options that can be given
+    before "get" that enable extra repositories. The full list is
+    given at the end of this help. For example:
+
+    ./sync-all --testsuite get
+
+    would get the testsuite repository in addition to the usual set of
+    subrepositories.
+
+remote add <remote-name>
+remote rm <remote-name>
+remote set-url [--push] <remote-name>
+
+    Runs a "git remote" command on each subrepository, adjusting the
+    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
+
+    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:
+
+  branch
+  checkout
+  clean
+  commit
+  config
+  diff
+  fetch
+  format-patch
+  gc
+  grep
+  log
+  new
+  new-workdir
+  pull
+  push
+  repack
+  reset
+  send
+  status
+  tag
+
+-------------- Flags -------------------
+These flags are given *before* the command and modify the way sync-all behaves.
+Flags given *after* the command are passed to git.
+
+  -q says to be quiet, and -s to be silent.
+
+  --resume will restart a command that failed, from the repo at which it
+  failed. This means you don't need to wait while, e.g., "pull" goes through
+  all the repos it's just pulled, and tries to pull them again.
+
+  --ignore-failure says to ignore errors and move on to the next repository
+
+  -r repo says to use repo as the location of package repositories
+
+  --checked-out says that the remote repo is in checked-out layout, as opposed
+  to the layout used for the main repo. By default a repo on the local
+  filesystem is assumed to be checked-out, and repos accessed via HTTP or SSH
+  are assumed to be in the main repo layout; use --checked-out to override the
+  latter.
+
+  --bare says that the local repo is in bare layout, same as the main repo. It
+  also means that these repos are bare. You only have to use this flag if you
+  don't have a bare ghc.git in the current directory and would like to 'get'
+  all of the repos bare. Requires packages.conf to be present in the current
+  directory (a renamed packages file from the main ghc repo).
+
+  Note: --checked-out and --bare flags are NOT the opposite of each other.
+        --checked-out: describes the layout of the remote repository tree.
+        --bare:        describes the layout of the local repository tree.
+
+  --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
+
+
+------------ Checking out a branch -------------
+To check out a branch you can run the following command:
+
+  \$ ./sync-all checkout ghc-7.4
+
+
+------------ Which repos to use -------------
+sync-all uses the following algorithm to decide which remote repos to use
+
+It always computes the remote repos from a single base, <repo_base> How is
+<repo_base> set? If you say "-r repo", then that's <repo_base> otherwise
+<repo_base> is set by asking git where the ghc repo came from, and removing the
+last component (e.g. /ghc.git/ or /ghc/).
+
+Then sync-all iterates over the package found in the file ./packages; see that
+file for a description of the contents.
+
+If <repo_base> looks like a local filesystem path, or if you give the
+--checked-out flag, sync-all works on repos of form:
+
+  <repo_base>/<local-path>
+
+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
+  and     sync-all -r ../working remote add working
+The latter is called a "checked-out tree".
+
+sync-all *ignores* the defaultrepo of all repos other than the root one. So the
+remote repos must be laid out in one of the two formats given by <local-path>
+and <remote-path> in the file 'packages'.
 
 Available package-tags are:
 END
 
         # Collect all the tags in the packages file
         my %available_tags;
-        open IN, "< packages" or die "Can't open packages file";
+        open IN, "< packages.conf"
+            or open IN, "< packages" # clashes with packages directory when using --bare
+            or die "Can't open packages file (or packages.conf)";
         while (<IN>) {
             chomp;
             if (/^([^# ]+) +(?:([^ ]+) +)?([^ ]+) +([^ ]+)/) {
@@ -459,17 +743,14 @@ END
             }
         }
         close IN;
-        
+
         # Show those tags and the help text
         my @available_tags = keys %available_tags;
-        print "$help@available_tags\n";
-        exit 1;
+        print "$help@available_tags\n\n";
+        exit $exit;
 }
 
 sub main {
-    if (! -d ".git" || ! -d "compiler") {
-        die "error: sync-all must be run from the top level of the ghc tree."
-    }
 
     $tags{"-"} = 1;
     $tags{"dph"} = 1;
@@ -487,17 +768,28 @@ sub main {
         elsif ($arg eq "-r") {
             $defaultrepo = shift;
         }
+        elsif ($arg eq "--resume") {
+            $try_to_resume = 1;
+        }
         elsif ($arg eq "--ignore-failure") {
             $ignore_failure = 1;
         }
         elsif ($arg eq "--complete" || $arg eq "--partial") {
             $get_mode = $arg;
         }
-        # Use --checked-out if the remote repos are a checked-out tree,
+        # Use --checked-out if the _remote_ repos are a checked-out tree,
         # rather than the master trees.
         elsif ($arg eq "--checked-out") {
             $checked_out_flag = 1;
         }
+        # Use --bare if the _local_ repos are bare repos,
+        # rather than a checked-out tree.
+        elsif ($arg eq "--bare") {
+            $bare_flag = $arg;
+        }
+        elsif ($arg eq "--help") {
+            help(0);
+        }
         # --<tag> says we grab the libs tagged 'tag' with
         # 'get'. It has no effect on the other commands.
         elsif ($arg =~ m/^--no-(.*)$/) {
@@ -515,18 +807,82 @@ sub main {
         }
     }
 
+    # check for ghc repositories in cwd
+    my $checked_out_found = 1 if (-d ".git" && -d "compiler");
+    my $bare_found = 1 if (-d "ghc.git");
+
+    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"
+    }
+    elsif ($bare_found) {
+        $bare_flag = "--bare";
+    }
+    elsif (! $bare_flag && ! $checked_out_found) {
+        die "error: sync-all must be run from the top level of the ghc tree.";
+    }
+
     if ($#_ eq -1) {
-        help();
+        help(1);
     }
     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") {
+            &git(".", "submodule", "init", @submodule_args);
+        }
+        if ($command eq "pull") {
+            my $gitConfig = &tryReadFile(".git/config");
+            if ($gitConfig !~ /submodule/) {
+                &git(".", "submodule", "init", @submodule_args);
+            }
+        }
+        if ($command eq "get" or $command eq "pull") {
+            my $gitConfig = &tryReadFile(".git/config");
+            if ($gitConfig !~ /submodule/) {
+                &git(".", "submodule", "init", @submodule_args);
+            }
+            &git(".", "submodule", "update", @submodule_args);
+        }
     }
 }
 
+BEGIN {
+    $initial_working_directory = getcwd();
+}
+
 END {
     my $ec = $?;
-    my $pwd = getcwd();
+
+    chdir($initial_working_directory);
 
     message "== Checking for old haddock repo";
     if (-d "utils/haddock/.git") {
@@ -539,11 +895,11 @@ ATTENTION!
 You have an old haddock repository in your GHC tree!
 
 Please remove it (e.g. "rm -r utils/haddock"), and then run
-"./syncs-all get" to get the new repository.
+"./sync-all get" to get the new repository.
 ============================
 EOF
         }
-        chdir($pwd);
+        chdir($initial_working_directory);
     }
 
     message "== Checking for old binary repo";
@@ -557,11 +913,61 @@ ATTENTION!
 You have an old binary repository in your GHC tree!
 
 Please remove it (e.g. "rm -r libraries/binary"), and then run
-"./syncs-all get" to get the new repository.
+"./sync-all get" to get the new repository.
 ============================
 EOF
         }
-        chdir($pwd);
+        chdir($initial_working_directory);
+    }
+
+    message "== Checking for old mtl repo";
+    if (-d "libraries/mtl/.git") {
+        chdir("libraries/mtl");
+        if ((system "git log -1 c67d8f7247c612dc35242bc67e616f7ea35eadb9 > /dev/null 2> /dev/null") == 0) {
+            print <<EOF;
+============================
+ATTENTION!
+
+You have an old mtl repository in your GHC tree!
+
+Please remove it (e.g. "rm -r libraries/mtl"), and then run
+"./sync-all get" to get the new repository.
+============================
+EOF
+        }
+        chdir($initial_working_directory);
+    }
+
+    message "== Checking for old Cabal repo";
+    if (-d "libraries/Cabal/.git") {
+        chdir("libraries/Cabal");
+        if ((system "git log -1 c8ebd66a32865f72ae03ee0663c62df3d77f08fe > /dev/null 2> /dev/null") == 0) {
+            print <<EOF;
+============================
+ATTENTION!
+
+You have an old Cabal repository in your GHC tree!
+
+Please remove it (e.g. "rm -r libraries/Cabal"), and then run
+"./sync-all get" to get the new repository.
+============================
+EOF
+        }
+        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
     }
 
     $? = $ec;