37f2d813a9e94f8c756df8be1a635228d47a5547
[ghc-hetmet.git] / sync-all
1 #!/usr/bin/perl -w
2
3 use strict;
4 use Cwd;
5
6 # Usage:
7 #
8 # ./sync-all [-q] [-s] [--ignore-failure] [-r repo]
9 #            [--nofib] [--testsuite] [--checked-out] cmd [git flags]
10 #
11 # Applies the command "cmd" to each repository in the tree.
12 # sync-all will try to do the right thing for both git and darcs repositories.
13 #
14 # e.g.
15 #      ./sync-all -r http://darcs.haskell.org/ghc get
16 #          To get any repos which do not exist in the local tree
17 #
18 #      ./sync-all pull
19 #          To pull everything from the default repos
20 #
21 # -------------- Flags -------------------
22 #   -q says to be quite, and -s to be silent.
23 #
24 #   --ignore-failure says to ignore errors and move on to the next repository
25 #
26 #   -r repo says to use repo as the location of package repositories
27 #
28 #   --checked-out says that the remote repo is in checked-out layout, as
29 #   opposed to the layout used for the main repo.  By default a repo on
30 #   the local filesystem is assumed to be checked-out, and repos accessed
31 #   via HTTP or SSH are assumed to be in the main repo layout; use
32 #   --checked-out to override the latter.
33 #
34 #   --nofib, --testsuite also get the nofib and testsuite repos respectively
35 #
36 # ------------ Which repos to use -------------
37 # sync-all uses the following algorithm to decide which remote repos to use
38 #
39 #  It always computes the remote repos from a single base, $repo_base
40 #  How is $repo_base set?  
41 #    If you say "-r repo", then that's $repo_base
42 #    otherwise $repo_base is set by asking git where the ghc repo came
43 #    from, and removing the last component (e.g. /ghc.git/ of /ghc/).
44 #
45 #  Then sync-all iterates over the package found in the file
46 #  ./packages; see that file for a description of the contents.
47
48 #    If $repo_base looks like a local filesystem path, or if you give
49 #    the --checked-out flag, sync-all works on repos of form
50 #          $repo_base/<local-path>
51 #    otherwise sync-all works on repos of form
52 #          $repo_base/<remote-path>
53 #    This logic lets you say
54 #      both    sync-all -r http://darcs.haskell.org/ghc-6.12 pull
55 #      and     sync-all -r ../HEAD pull
56 #    The latter is called a "checked-out tree".
57
58 # NB: sync-all *ignores* the defaultrepo of all repos other than the
59 # root one.  So the remote repos must be laid out in one of the two
60 # formats given by <local-path> and <remote-path> in the file 'packages'.
61
62 my $defaultrepo;
63 my @packages;
64 my $verbose = 2;
65 my $ignore_failure = 0;
66 my $want_remote_repo = 0;
67 my $checked_out_flag = 0;
68 my $get_mode;
69
70 # Flags specific to a particular command
71 my $local_repo_unnecessary = 0;
72
73 my %tags;
74
75 # Figure out where to get the other repositories from.
76 sub getrepo {
77     my $basedir = ".";
78     my $repo;
79
80     if (defined($defaultrepo)) {
81         $repo = $defaultrepo;
82         chomp $repo;
83     } else {
84         # Figure out where to get the other repositories from,
85         # based on where this GHC repo came from.
86         my $branch = `git branch | grep "\* " | sed "s/^\* //"`; chomp $branch;
87         my $remote = `git config branch.$branch.remote`;         chomp $remote;
88         $repo = `git config remote.$remote.url`;       chomp $repo;
89     }
90
91     my $repo_base;
92     my $checked_out_tree;
93
94     if ($repo =~ /^...*:/) {
95         # HTTP or SSH
96         # Above regex says "at least two chars before the :", to avoid
97         # catching Win32 drives ("C:\").
98         $repo_base = $repo;
99
100         # --checked-out is needed if you want to use a checked-out repo
101         # over SSH or HTTP
102         if ($checked_out_flag) {
103             $checked_out_tree = 1;
104         } else {
105             $checked_out_tree = 0;
106         }
107
108         # Don't drop the last part of the path if specified with -r, as
109         # it expects repos of the form:
110         #
111         #   http://darcs.haskell.org
112         #
113         # rather than
114         #   
115         #   http://darcs.haskell.org/ghc
116         #
117         if (!$defaultrepo) {
118             $repo_base =~ s#/[^/]+/?$##;
119         }
120     }
121     elsif ($repo =~ /^\/|\.\.\/|.:(\/|\\)/) {
122         # Local filesystem, either absolute or relative path
123         # (assumes a checked-out tree):
124         $repo_base = $repo;
125         $checked_out_tree = 1;
126     }
127     else {
128         die "Couldn't work out repo";
129     }
130
131     return $repo_base, $checked_out_tree;
132 }
133
134 sub parsePackages {
135     my @repos;
136     my $lineNum;
137
138     open IN, "< packages" or die "Can't open packages file";
139     @repos = <IN>;
140     close IN;
141
142     @packages = ();
143     $lineNum = 0;
144     foreach (@repos) {
145         chomp;
146         $lineNum++;
147         if (/^([^# ]+) +([^ ]+) +([^ ]+) +([^ ]+) +([^ ]+)$/) {
148             my %line;
149             $line{"localpath"}  = $1;
150             $line{"tag"}        = $2;
151             $line{"remotepath"} = $3;
152             $line{"vcs"}        = $4;
153             $line{"upstream"}   = $5;
154             push @packages, \%line;
155         }
156         elsif (! /^(#.*)?$/) {
157             die "Bad content on line $lineNum of packages file: $_";
158         }
159     }
160 }
161
162 sub message {
163     if ($verbose >= 2) {
164         print "@_\n";
165     }
166 }
167
168 sub warning {
169     if ($verbose >= 1) {
170         print "warning: @_\n";
171     }
172 }
173
174 sub scm {
175     my $dir = shift;
176     my $scm = shift;
177     my $pwd;
178
179     if ($dir eq '.') {
180         message "== running $scm @_";
181     } else {
182         message "== $dir: running $scm @_";
183         $pwd = getcwd();
184         chdir($dir);
185     }
186
187     system ($scm, @_) == 0
188         or $ignore_failure
189         or die "$scm failed: $?";
190
191     if ($dir ne '.') {
192         chdir($pwd);
193     }
194 }
195
196 sub repoexists {
197     my ($scm, $localpath) = @_;
198     
199     if ($scm eq "darcs") {
200         -d "$localpath/_darcs";
201     }
202     else {
203         -d "$localpath/.git";
204     }
205 }
206
207 sub scmall {
208     my $command = shift;
209     
210     my $localpath;
211     my $tag;
212     my $remotepath;
213     my $scm;
214     my $upstream;
215     my $line;
216
217     my $path;
218     my $wd_before = getcwd;
219
220     my @scm_args;
221
222     my $pwd;
223
224     my ($repo_base, $checked_out_tree) = getrepo();
225
226     parsePackages;
227
228     for $line (@packages) {
229
230             $localpath  = $$line{"localpath"};
231             $tag        = $$line{"tag"};
232             $remotepath = $$line{"remotepath"};
233             $scm        = $$line{"vcs"};
234             $upstream   = $$line{"upstream"};
235
236             # Check the SCM is OK as early as possible
237             die "Unknown SCM: $scm" if (($scm ne "darcs") and ($scm ne "git"));
238
239             # Work out the path for this package in the repo we pulled from
240             if ($checked_out_tree) {
241                 $path = "$repo_base/$localpath";
242             }
243             else {
244                 $path = "$repo_base/$remotepath";
245             }
246
247             # Work out the arguments we should give to the SCM
248             if ($command =~ /^(?:w|wh|wha|what|whats|whatsn|whatsne|whatsnew)$/) {
249                 @scm_args = (($scm eq "darcs" and "whatsnew")
250                           or ($scm eq "git" and "status"));
251                 
252                 # Hack around 'darcs whatsnew' failing if there are no changes
253                 $ignore_failure = 1;
254             }
255             elsif ($command =~ /^(?:pus|push)$/) {
256                 @scm_args = "push";
257                 $want_remote_repo = 1;
258             }
259             elsif ($command =~ /^(?:pul|pull)$/) {
260                 @scm_args = "pull";
261                 $want_remote_repo = 1;
262                 # Q: should we append the -a argument for darcs repos?
263             }
264             elsif ($command =~ /^(?:g|ge|get)$/) {
265                 # Skip any repositories we have not included the tag for
266                 if (not defined($tags{$tag})) {
267                     next;
268                 }
269                 
270                 if (-d $localpath) {
271                     warning("$localpath already present; omitting") if $localpath ne ".";
272                     next;
273                 }
274                 
275                 # The first time round the loop, default the get-mode
276                 if (not defined($get_mode)) {
277                     warning("adding --partial, to override use --complete");
278                     $get_mode = "--partial";
279                 }
280                 
281                 # The only command that doesn't need a repo
282                 $local_repo_unnecessary = 1;
283                 
284                 if ($scm eq "darcs") {
285                     # Note: we can only use the get-mode with darcs for now
286                     @scm_args = ("get", $get_mode, $path, $localpath);
287                 }
288                 else {
289                     @scm_args = ("clone", $path, $localpath);
290                 }
291             }
292             elsif ($command =~ /^(?:s|se|sen|send)$/) {
293                 @scm_args = (($scm eq "darcs" and "send")
294                           or ($scm eq "git" and "send-email"));
295                 $want_remote_repo = 1;
296             }
297             elsif ($command =~ /^set-origin$/) {
298                 @scm_args = ("remote", "set-url", "origin", $path);
299             }
300             elsif ($command =~ /^fetch$/) {
301                 @scm_args = ("fetch", "origin");
302             }
303             elsif ($command =~ /^new$/) {
304                 @scm_args = ("log", "origin..");
305             }
306             else {
307                 die "Unknown command: $command";
308             }
309             
310             # Actually execute the command
311             if (repoexists ($scm, $localpath)) {
312                 if ($want_remote_repo) {
313                     if ($scm eq "darcs") {
314                         scm (".", $scm, @scm_args, @_, "--repodir=$localpath", $path);
315                     } else {
316                         # git pull doesn't like to be used with --work-dir
317                         # I couldn't find an alternative to chdir() here
318                         scm ($localpath, $scm, @scm_args, @_, $path, "master");
319                     }
320                 } else {
321                     # git status *must* be used with --work-dir, if we don't chdir() to the dir
322                     scm ($localpath, $scm, @scm_args, @_);
323                 }
324             }
325             elsif ($local_repo_unnecessary) {
326                 # Don't bother to change directory in this case
327                 scm (".", $scm, @scm_args, @_);
328             }
329             elsif ($tag eq "") {
330                 message "== Required repo $localpath is missing! Skipping";
331             }
332             else {
333                 message "== $localpath repo not present; skipping";
334             }
335     }
336 }
337
338 sub main {
339     if (! -d ".git" || ! -d "compiler") {
340         die "error: sync-all must be run from the top level of the ghc tree."
341     }
342
343     $tags{"-"} = 1;
344     $tags{"dph"} = 1;
345
346     while ($#_ ne -1) {
347         my $arg = shift;
348         # We handle -q here as well as lower down as we need to skip over it
349         # if it comes before the source-control command
350         if ($arg eq "-q") {
351             $verbose = 1;
352         }
353         elsif ($arg eq "-s") {
354             $verbose = 0;
355         }
356         elsif ($arg eq "-r") {
357             $defaultrepo = shift;
358         }
359         elsif ($arg eq "--ignore-failure") {
360             $ignore_failure = 1;
361         }
362         elsif ($arg eq "--complete" || $arg eq "--partial") {
363             $get_mode = $arg;
364         }
365         # Use --checked-out if the remote repos are a checked-out tree,
366         # rather than the master trees.
367         elsif ($arg eq "--checked-out") {
368             $checked_out_flag = 1;
369         }
370         # --<tag> says we grab the libs tagged 'tag' with
371         # 'get'. It has no effect on the other commands.
372         elsif ($arg =~ m/^--/) {
373             $arg =~ s/^--//;
374             $tags{$arg} = 1;
375         }
376         else {
377             unshift @_, $arg;
378             if (grep /^-q$/, @_) {
379                 $verbose = 1;
380             }
381             last;
382         }
383     }
384
385     if ($#_ eq -1) {
386         # Get the built in help
387         my $help = <<END;
388 What do you want to do?
389 Supported commands:
390
391  * whatsnew
392  * push
393  * pull
394  * get, with options:
395   * --<package-tag>
396   * --complete
397   * --partial
398  * fetch
399  * send
400  * set-origin
401  * new
402
403 Available package-tags are:
404 END
405
406         # Collect all the tags in the packages file
407         my %available_tags;
408         open IN, "< packages" or die "Can't open packages file";
409         while (<IN>) {
410             chomp;
411             if (/^([^# ]+) +(?:([^ ]+) +)?([^ ]+) +([^ ]+)/) {
412                 if (defined($2) && $2 ne "-") {
413                     $available_tags{$2} = 1;
414                 }
415             }
416             elsif (! /^(#.*)?$/) {
417                 die "Bad line: $_";
418             }
419         }
420         close IN;
421         
422         # Show those tags and the help text
423         my @available_tags = keys %available_tags;
424         print "$help@available_tags\n";
425         exit 1;
426     }
427     else {
428         # Give the command and rest of the arguments to the main loop
429         scmall @_;
430     }
431 }
432
433 main(@ARGV);
434