All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
7 mr - a Multiple Repository management tool
11 B<mr> [options] checkout
13 B<mr> [options] update
15 B<mr> [options] status
17 B<mr> [options] commit [-m "message"]
23 B<mr> [options] register [repository]
25 B<mr> [options] config section ["parameter=[value]" ...]
27 B<mr> [options] action [params ...]
31 B<mr> is a Multiple Repository management tool. It can checkout, update, or
32 perform other actions on a set of repositories as if they were one combined
33 respository. It supports any combination of subversion, git, cvs, mecurial and
34 bzr repositories, and support for other revision control systems can easily be
37 B<mr> cds into and operates on all registered repositories at or below your
38 working directory. Or, if you are in a subdirectory of a repository that
39 contains no other registered repositories, it will stay in that directory,
40 and work on only that repository,
42 These predefined commands should be fairly familiar to users of any revision
47 =item checkout (or co)
49 Checks out any repositories that are not already checked out.
53 Updates each repository from its configured remote repository.
55 If a repository isn't checked out yet, it will first check it out.
59 Displays a status report for each repository, showing what
60 uncommitted changes are present in the repository.
64 Commits changes to each repository. (By default, changes are pushed to the
65 remote repository too, when using distributed systems like git.)
67 The optional -m parameter allows specifying a commit message.
71 Show a diff of uncommitted changes.
79 These commands are also available:
85 List the repositories that mr will act on.
89 Register an existing repository in a mrconfig file. By default, the
90 repository in the current directory is registered, or you can specify a
91 directory to register.
93 The mrconfig file that is modified is chosen by either the -c option, or by
94 looking for the closest known one at or below the current directory.
98 Adds, modifies, removes, or prints a value from a mrconfig file. The next
99 parameter is the name of the section the value is in. To add or modify
100 values, use one or more instances of "parameter=value". Use "parameter=" to
101 remove a parameter. Use just "parameter" to get the value of a parameter.
103 For example, to add (or edit) a repository in src/foo:
105 mr config src/foo checkout="svn co svn://example.com/foo/trunk foo"
107 To show the command that mr uses to update the repository in src/foo:
109 mr config src/foo update
111 To see the built-in library of shell functions contained in mr:
113 mr config DEFAULT lib
115 The ~/.mrconfig file is used by default. To use a different config file,
124 Actions can be abbreviated to any unambiguous subsctring, so
125 "mr st" is equivilant to "mr status", and "mr up" is equivilant to "mr
128 Additional parameters can be passed to most commands, and are passed on
129 unchanged to the underlying revision control system. This is mostly useful
130 if the repositories mr will act on all use the same revision control
139 Specifies the topmost directory that B<mr> should work in. The default is
140 the current working directory.
144 Use the specified mrconfig file. The default is B<~/.mrconfig>
152 Expand the statistics line displayed at the end to include information
153 about exactly which repositories failed and were skipped, if any.
157 Just operate on the repository for the current directory, do not
158 recurse into deeper repositories.
162 Run the specified number of jobs in parallel. This can greatly speed up
163 operations such as updates. It is not recommended for interactive
170 B<mr> is configured by .mrconfig files. It starts by reading the .mrconfig
171 file in your home directory, and this can in turn chain load .mrconfig files
174 Here is an example .mrconfig file:
177 checkout = svn co svn://svn.example.com/src/trunk src
181 checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git &&
183 git checkout -b mybranch origin/master
185 The .mrconfig file uses a variant of the INI file format. Lines starting with
186 "#" are comments. Values can be continued to the following line by
187 indenting the line with whitespace.
189 The "DEFAULT" section allows setting default values for the sections that
192 The "ALIAS" section allows adding aliases for actions. Each parameter
193 is an alias, and its value is the action to use.
195 All other sections add repositories. The section header specifies the
196 directory where the repository is located. This is relative to the directory
197 that contains the mrconfig file, but you can also choose to use absolute
200 Within a section, each parameter defines a shell command to run to handle a
201 given action. mr contains default handlers for "update", "status",
202 "commit", and other standard actions. Normally you only need to specify what
203 to do for "checkout".
205 Note that these shell commands are run in a "set -e" shell
206 environment, where any additional parameters you pass are available in
207 "$@". The "checkout" command is run in the parent of the repository
208 directory, since the repository isn't checked out yet. All other commands
209 are run inside the repository, though not necessarily at the top of it.
211 The "MR_REPO" environment variable is set to the path to the top of the
212 repository. The "MR_CONFIG" environment variable is set to the .mrconfig file
213 that defines the repo being acted on, or, if the repo is not yet in a config
214 file, the .mrconfig file that should be modified to register the repo.
216 A few parameters have special meanings:
222 If the "skip" parameter is set and its command returns true, then B<mr>
223 will skip acting on that repository. The command is passed the action
226 Here are two examples. The first skips the repo unless
227 mr is run by joey. The second uses the hours_since function
228 (included in mr's built-in library) to skip updating the repo unless it's
229 been at least 12 hours since the last update.
231 skip = test $(whoami) != joey
232 skip = [ "$1" = update ] && ! hours_since "$1" 12
236 The "order" parameter can be used to override the default ordering of
237 repositories. The default order value is 10. Use smaller values to make
238 repositories be processed earlier, and larger values to make repositories
241 Note that if a repository is located in a subdirectory of another
242 repository, ordering it to be processed earlier is not recommended, as this
243 can cause confusion during checkouts.
247 If the "chain" parameter is set and its command returns true, then B<mr>
248 will try to load a .mrconfig file from the root of the repository. (You
249 should avoid chaining from repositories with untrusted committers.)
253 The "lib" parameter can specify some shell code that will be run before each
254 command, this can be a useful way to define shell functions for other commands
261 Copyright 2007 Joey Hess <joey@kitenet.net>
263 Licensed under the GNU GPL version 2 or higher.
265 http://kitenet.net/~joey/code/mr/
274 use Cwd qw(getcwd abs_path);
284 print STDERR "mr: interrupted\n";
288 $ENV{MR_CONFIG}="$ENV{HOME}/.mrconfig";
289 my $config_overridden=0;
290 my $directory=getcwd();
300 Getopt::Long::Configure("no_permute");
301 my $result=GetOptions(
302 "d|directory=s" => sub { $directory=abs_path($_[1]) },
303 "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
304 "v|verbose" => \$verbose,
305 "s|stats" => \$stats,
306 "n|no-recurse" => \$no_recurse,
307 "j|jobs=i" => \$jobs,
309 if (! $result || @ARGV < 1) {
310 die("Usage: mr [-d directory] action [params ...]\n".
311 "(Use mr help for man page.)\n");
315 # Make sure MR_CONFIG is an absolute path, but don't use abs_path since
316 # the config file might be a symlink to elsewhere, and the directory it's
318 if ($ENV{MR_CONFIG} !~ /^\//) {
319 $ENV{MR_CONFIG}=getcwd()."/".$ENV{MR_CONFIG};
321 # Try to set MR_PATH to the path to the program.
323 use FindBin qw($Bin $Script);
324 $ENV{MR_PATH}=$Bin."/".$Script;
328 loadconfig($ENV{MR_CONFIG});
330 #print Dumper(\%config);
332 # alias expansion and command stemming
333 my $action=shift @ARGV;
334 if (exists $alias{$action}) {
335 $action=$alias{$action};
337 if (! exists $knownactions{$action}) {
338 my @matches = grep { /^\Q$action\E/ }
339 keys %knownactions, keys %alias;
343 elsif (@matches == 0) {
344 die "mr: unknown action \"$action\" (known actions: ".
345 join(", ", sort keys %knownactions).")\n";
348 die "mr: ambiguous action \"$action\" (matches: ".
349 join(", ", @matches).")\n";
353 # commands that do not operate on all repos
354 if ($action eq 'help') {
355 exec($config{''}{DEFAULT}{$action}) || die "exec: $!";
357 elsif ($action eq 'config') {
359 die "mr config: not enough parameters\n";
362 if ($section=~/^\//) {
363 # try to convert to a path relative to the config file
364 my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
366 $dir.="/" unless $dir=~/\/$/;
367 if ($section=~/^\Q$dir\E(.*)/) {
373 if (/^([^=]+)=(.*)$/) {
374 $changefields{$1}=$2;
378 foreach my $topdir (sort keys %config) {
379 if (exists $config{$topdir}{$section} &&
380 exists $config{$topdir}{$section}{$_}) {
381 print $config{$topdir}{$section}{$_}."\n";
383 last if $section eq 'DEFAULT';
387 die "mr $action: $section $_ not set\n";
391 modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
394 elsif ($action eq 'register') {
395 if (! $config_overridden) {
396 # Find the closest known mrconfig file to the current
398 $directory.="/" unless $directory=~/\/$/;
399 foreach my $topdir (reverse sort keys %config) {
400 next unless length $topdir;
401 if ($directory=~/^\Q$topdir\E/) {
402 $ENV{MR_CONFIG}=$configfiles{$topdir};
407 my $command="set -e; ".$config{''}{DEFAULT}{lib}."\n".
408 "my_action(){ $config{''}{DEFAULT}{$action}\n }; my_action ".
409 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
410 print STDERR "mr $action: running >>$command<<\n" if $verbose;
411 exec($command) || die "exec: $!";
414 # an ordered list of repos
416 foreach my $topdir (sort keys %config) {
417 foreach my $subdir (sort keys %{$config{$topdir}}) {
421 order => $config{$topdir}{$subdir}{order},
426 $a->{order} <=> $b->{order}
428 $a->{topdir} cmp $b->{topdir}
430 $a->{subdir} cmp $b->{subdir}
433 # work out what repos to act on
436 foreach my $repo (@list) {
437 my $topdir=$repo->{topdir};
438 my $subdir=$repo->{subdir};
440 next if $subdir eq 'DEFAULT';
441 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
443 $dir.="/" unless $dir=~/\/$/;
444 $d.="/" unless $d=~/\/$/;
445 next if $no_recurse && $d ne $dir;
446 next if $dir ne $d && $dir !~ /^\Q$d\E/;
447 push @repos, [$dir, $topdir, $subdir];
450 # fallback to find a leaf repo
451 foreach my $repo (@list) {
452 my $topdir=$repo->{topdir};
453 my $subdir=$repo->{subdir};
455 next if $subdir eq 'DEFAULT';
456 my $dir=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
458 $dir.="/" unless $dir=~/\/$/;
459 $d.="/" unless $d=~/\/$/;
460 if ($d=~/^\Q$dir\E/) {
461 push @repos, [$dir, $topdir, $subdir];
468 # run the action on each repository and print stats
469 my (@ok, @failed, @skipped);
474 foreach my $repo (@repos) {
475 record($repo, action($action, @$repo));
479 if (! @ok && ! @failed && ! @skipped) {
480 die "mr $action: no repositories found to work on\n";
482 print "mr $action: finished (".join("; ",
483 showstat($#ok+1, "ok", "ok"),
484 showstat($#failed+1, "failed", "failed"),
485 showstat($#skipped+1, "skipped", "skipped"),
489 print "mr $action: (skipped: ".join(" ", @skipped).")\n";
492 print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
498 elsif (! @ok && @skipped) {
504 my ($action, $dir, $topdir, $subdir) = @_;
506 $ENV{MR_CONFIG}=$configfiles{$topdir};
507 my $lib=exists $config{$topdir}{$subdir}{lib} ?
508 $config{$topdir}{$subdir}{lib}."\n" : "";
510 if ($action eq 'checkout') {
512 print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
516 $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
519 print "mr $action: creating parent directory $dir\n" if $verbose;
520 system("mkdir", "-p", $dir);
523 elsif ($action =~ /update/) {
525 return action("checkout", $dir, $topdir, $subdir);
531 if (exists $config{$topdir}{$subdir}{skip}) {
532 my $test="set -e;".$lib.
533 "my_action(){ $config{$topdir}{$subdir}{skip}\n }; my_action '$action'";
534 print "mr $action: running skip test >>$test<<\n" if $verbose;
535 my $ret=system($test);
537 if (($? & 127) == 2) {
538 print STDERR "mr $action: interrupted\n";
542 print STDERR "mr $action: skip test received signal ".($? & 127)."\n";
546 if ($ret >> 8 == 0) {
547 print "mr $action: $dir skipped per config file\n" if $verbose;
552 if (! $nochdir && ! chdir($dir)) {
553 print STDERR "mr $action: failed to chdir to $dir: $!\n";
556 elsif (! exists $config{$topdir}{$subdir}{$action}) {
557 print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n";
562 print "mr $action: $topdir$subdir\n";
565 print "mr $action: $topdir$subdir (in subdir $directory)\n";
567 my $command="set -e; ".$lib.
568 "my_action(){ $config{$topdir}{$subdir}{$action}\n }; my_action ".
569 join(" ", map { s/\//\/\//g; s/"/\"/g; '"'.$_.'"' } @ARGV);
570 print STDERR "mr $action: running >>$command<<\n" if $verbose;
571 my $ret=system($command);
573 if (($? & 127) == 2) {
574 print STDERR "mr $action: interrupted\n";
578 print STDERR "mr $action: received signal ".($? & 127)."\n";
581 print STDERR "mr $action: failed ($ret)\n" if $verbose;
582 if ($ret >> 8 != 0) {
583 print STDERR "mr $action: command failed\n";
586 print STDERR "mr $action: command died ($ret)\n";
591 if ($action eq 'checkout' && ! -d $dir) {
592 print STDERR "mr $action: $dir missing after checkout\n";;
601 # run actions on multiple repos, in parallel
608 while (@fhs or @repos) {
609 while ($running < $jobs && @repos) {
611 my $repo = shift @repos;
612 pipe(my $outfh, CHILD_STDOUT);
613 pipe(my $errfh, CHILD_STDERR);
615 unless ($pid = fork) {
616 die "mr $action: cannot fork: $!" unless defined $pid;
617 open(STDOUT, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!";
618 open(STDERR, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!";
623 exit action($action, @$repo);
627 push @active, [$pid, $repo];
628 push @fhs, [$outfh, $errfh];
631 my ($rin, $rout) = ('','');
633 foreach my $fh (@fhs) {
634 next unless defined $fh;
635 vec($rin, fileno($fh->[0]), 1) = 1 if defined $fh->[0];
636 vec($rin, fileno($fh->[1]), 1) = 1 if defined $fh->[1];
638 $nfound = select($rout=$rin, undef, undef, 1);
639 foreach my $channel (0, 1) {
640 foreach my $i (0..$#fhs) {
641 next unless defined $fhs[$i];
642 my $fh = $fhs[$i][$channel];
643 next unless defined $fh;
644 if (vec($rout, fileno($fh), 1) == 1) {
646 if (sysread($fh, $r, 1024) == 0) {
648 $fhs[$i][$channel] = undef;
649 if (! defined $fhs[$i][0] &&
650 ! defined $fhs[$i][1]) {
651 waitpid($active[$i][0], 0);
652 print STDOUT $out[$i][0];
653 print STDERR $out[$i][1];
655 record($active[$i][1], $? >> 8);
657 splice(@active, $i, 1);
662 $out[$i][$channel] .= $r;
670 my $dir=shift()->[0];
676 elsif ($ret == FAILED) {
679 elsif ($ret == SKIPPED) {
682 elsif ($ret == ABORT) {
686 die "unknown exit status $ret";
695 return "$count ".($count > 1 ? $plural : $singular);
701 sub loadconfig { #{{{
708 if (ref $f eq 'GLOB') {
717 my $absf=abs_path($f);
718 if ($loaded{$absf}) {
723 ($dir)=$f=~/^(.*\/)[^\/]+$/;
724 if (! defined $dir) {
727 $dir=abs_path($dir)."/";
729 if (! exists $configfiles{$dir}) {
730 $configfiles{$dir}=$f;
733 # copy in defaults from first parent
735 while ($parent=~s/^(.*\/)[^\/]+\/?$/$1/) {
736 if ($parent eq '/') {
739 if (exists $config{$parent} &&
740 exists $config{$parent}{DEFAULT}) {
741 $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
746 print "mr: loading config $f\n" if $verbose;
747 open($in, "<", $f) || die "mr: open $f: $!\n";
758 next if /^\s*\#/ || /^\s*$/;
759 if (/^\[([^\]]*)\]\s*$/) {
762 elsif (/^(\w+)\s*=\s*(.*)/) {
767 while (@lines && $lines[0]=~/^\s(.+)/) {
774 if (! defined $section) {
775 die "$f line $.: parameter ($parameter) not in section\n";
777 if ($section ne 'ALIAS' &&
778 ! exists $config{$dir}{$section} &&
779 exists $config{$dir}{DEFAULT}) {
781 $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
783 if ($section eq 'ALIAS') {
784 $alias{$parameter}=$value;
786 elsif ($parameter eq 'lib') {
787 $config{$dir}{$section}{lib}.=$value."\n";
790 $config{$dir}{$section}{$parameter}=$value;
791 $knownactions{$parameter}=1;
792 if ($parameter eq 'chain' &&
793 length $dir && $section ne "DEFAULT" &&
794 -e $dir.$section."/.mrconfig") {
795 my $ret=system($value);
797 if (($? & 127) == 2) {
798 print STDERR "mr $action: chain test interrupted\n";
802 print STDERR "mr $action: chain test received signal ".($? & 127)."\n";
806 push @toload, $dir.$section."/.mrconfig";
812 die "$f line $line: parse error\n";
821 sub modifyconfig { #{{{
823 # the section to modify or add
824 my $targetsection=shift;
825 # fields to change in the section
826 # To remove a field, set its value to "".
833 open(my $in, "<", $f) || die "mr: open $f: $!\n";
838 my $formatfield=sub {
840 my @value=split(/\n/, shift);
842 return "$field = ".shift(@value)."\n".
843 join("", map { "\t$_\n" } @value);
847 while ($out[$#out] =~ /^\s*$/) {
848 unshift @blanks, pop @out;
850 foreach my $field (sort keys %changefields) {
851 if (length $changefields{$field}) {
852 push @out, "$field = $changefields{$field}\n";
853 delete $changefields{$field};
863 if (/^\s*\#/ || /^\s*$/) {
866 elsif (/^\[([^\]]*)\]\s*$/) {
867 if (defined $section &&
868 $section eq $targetsection) {
876 elsif (/^(\w+)\s*=\s(.*)/) {
881 while (@lines && $lines[0]=~/^\s(.+)/) {
887 if ($section eq $targetsection) {
888 if (exists $changefields{$parameter}) {
889 if (length $changefields{$parameter}) {
890 $value=$changefields{$parameter};
892 delete $changefields{$parameter};
896 push @out, $formatfield->($parameter, $value);
900 if (defined $section &&
901 $section eq $targetsection) {
904 elsif (%changefields) {
905 push @out, "\n[$targetsection]\n";
906 foreach my $field (sort keys %changefields) {
907 if (length $changefields{$field}) {
908 push @out, $formatfield->($field, $changefields{$field});
913 open(my $out, ">", $f) || die "mr: write $f: $!\n";
918 # Finally, some useful actions that mr knows about by default.
919 # These can be overridden in ~/.mrconfig.
935 if [ -z "$1" ] || [ -z "$2" ]; then
936 error "mr: usage: hours_since action num"
938 for dir in .git .svn .bzr CVS .hg; do
939 if [ -e "$MR_REPO/$dir" ]; then
940 flagfile="$MR_REPO/$dir/.mr_last$1"
944 if [ -z "$flagfile" ]; then
945 error "cannot determine flag filename"
947 delta=$(perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile")
948 if [ "$delta" -lt "$2" ]; then
957 if [ -d "$MR_REPO"/.svn ]; then
959 elif [ -d "$MR_REPO"/.git ]; then
961 git pull -t origin master
965 elif [ -d "$MR_REPO"/.bzr ]; then
967 elif [ -d "$MR_REPO"/CVS ]; then
969 elif [ -d "$MR_REPO"/.hg ]; then
970 hg pull "$@" && hg update "$@"
972 error "unknown repo type"
975 if [ -d "$MR_REPO"/.svn ]; then
977 elif [ -d "$MR_REPO"/.git ]; then
978 git status "$@" || true
979 elif [ -d "$MR_REPO"/.bzr ]; then
981 elif [ -d "$MR_REPO"/CVS ]; then
983 elif [ -d "$MR_REPO"/.hg ]; then
986 error "unknown repo type"
989 if [ -d "$MR_REPO"/.svn ]; then
991 elif [ -d "$MR_REPO"/.git ]; then
992 git commit -a "$@" && git push --all
993 elif [ -d "$MR_REPO"/.bzr ]; then
994 bzr commit "$@" && bzr push
995 elif [ -d "$MR_REPO"/CVS ]; then
997 elif [ -d "$MR_REPO"/.hg ]; then
998 hg commit -m "$@" && hg push
1000 error "unknown repo type"
1003 if [ -d "$MR_REPO"/.svn ]; then
1005 elif [ -d "$MR_REPO"/.git ]; then
1007 elif [ -d "$MR_REPO"/.bzr ]; then
1009 elif [ -d "$MR_REPO"/CVS ]; then
1011 elif [ -d "$MR_REPO"/.hg ]; then
1014 error "unknown repo type"
1017 if [ -d "$MR_REPO"/.svn ]; then
1019 elif [ -d "$MR_REPO"/.git ]; then
1021 elif [ -d "$MR_REPO"/.bzr ]; then
1023 elif [ -d "$MR_REPO"/CVS ]; then
1025 elif [ -d "$MR_REPO"/.hg ]; then
1028 error "unknown repo type"
1031 if [ -n "$1" ]; then
1034 basedir="$(basename $(pwd))"
1035 if [ -d .svn ]; then
1036 url=$(LANG=C svn info . | grep -i ^URL: | cut -d ' ' -f 2)
1037 if [ -z "$url" ]; then
1038 error "cannot determine svn url"
1040 echo "Registering svn url: $url in $MR_CONFIG"
1041 mr -c "$MR_CONFIG" config "$(pwd)" checkout="svn co $url $basedir"
1042 elif [ -d .git ]; then
1043 url=$(LANG=C git-config --get remote.origin.url)
1044 if [ -z "$url" ]; then
1045 error "cannot determine git url"
1047 echo "Registering git url: $url in $MR_CONFIG"
1048 mr -c "$MR_CONFIG" config "$(pwd)" checkout="git clone $url $basedir"
1049 elif [ -d .bzr ]; then
1050 url=$(cat .bzr/branch/parent)
1051 if [ -z "$url" ]; then
1052 error "cannot determine bzr url"
1054 echo "Registering bzr url: $url in $MR_CONFIG"
1055 mr -c "$MR_CONFIG" config "$(pwd)" checkout="bzr clone $url $basedir"
1056 elif [ -d CVS ]; then
1057 repo=$(cat CVS/Repository)
1058 root=$(cat CVS/Root)
1059 if [ -z "$root" ]; then
1060 error "cannot determine cvs root"
1062 echo "Registering cvs repository $repo at root $root"
1063 mr -c "$MR_CONFIG" config "$(pwd)" \
1064 checkout="cvs -d '$root' co -d $basedir $repo"
1065 elif [ -d .hg ]; then
1066 url=$(hg showconfig paths.default)
1067 echo "Registering mercurial repo url: $url in $MR_CONFIG"
1068 mr -c "$MR_CONFIG" config "$(pwd)" \
1069 checkout="hg clone $url $basedir"
1071 error "unable to register this repo type"
1074 if [ ! -e "$MR_PATH" ]; then
1075 error "cannot find program path"
1077 (pod2man -c mr "$MR_PATH" | man -l -) || error "pod2man or man failed"
1081 ed = echo "A horse is a horse, of course, of course.."
1082 T = echo "I pity the fool."
1083 right = echo "Not found."