--- /dev/null
+debian/changelog merge=dpkg-mergechangelogs
--- /dev/null
+debian/files
+debian/mr.debhelper.log
+debian/mr.substvars
+debian/mr/
+mr.1
+webcheckout.1
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
--- /dev/null
+PREFIX:=/usr
+
+mans=mr.1 webcheckout.1
+
+build: $(mans)
+
+mr.1: mr
+ pod2man -c mr mr > mr.1
+
+webcheckout.1: webcheckout
+ pod2man -c webcheckout webcheckout > webcheckout.1
+
+test:
+ (echo "[.]"; echo "checkout=") > mrconfig.tmp
+ ./mr --trust-all -c mrconfig.tmp ed | grep -q "horse"
+ rm -f mrconfig.tmp
+
+install: build
+ install -d ${DESTDIR}${PREFIX}/bin
+ install -d ${DESTDIR}${PREFIX}/share/man/man1
+ install -d ${DESTDIR}${PREFIX}/share/mr
+
+ install -m0755 mr ${DESTDIR}${PREFIX}/bin/
+ install -m0755 webcheckout ${DESTDIR}${PREFIX}/bin/
+
+ install -m0644 mr.1 ${DESTDIR}${PREFIX}/share/man/man1/
+ install -m0644 webcheckout.1 ${DESTDIR}${PREFIX}/share/man/man1/
+
+ install -m0644 lib/* ${DESTDIR}${PREFIX}/share/mr/
+
+clean:
+ rm -f $(mans)
--- /dev/null
+mr is a Multiple Repository management tool for git, svn, mercurial, bzr,
+darcs, cvs, fossil and veracity.
+
+Author: Joey Hess
+Homepage: http://joeyh.name/code/mr/
+
+mr is intended to be very self-contained, since it might be useful to check
+it into ~/bin when keeping your home in version control. It has no
+dependencies aside from basic perl. (The included webcheckout command has
+more dependencies, specifically the LWP::Simple and HTML::Parser CPAN
+modules, and optionally the URI module.)
+
+To install mr, just copy mr into your PATH somewhere.
+
+To get started using mr, perhaps you already have some checked out
+repositories. Go into each one and run "mr register". Now mr has
+a list of them in ~/.mrconfig, which you can edit later to tune its
+operation.
+
+Suppose you've cd'd to ~/src, and it has many repositories under it.
+To update them all, run "mr update". To commit any pending changes in
+each, run "mr commit". To check the status of each, you could run
+"mr status".
+
+For further details, and lots of configuration options, see the mr(1) man
+page.
--- /dev/null
+* a way to detect repos in a tree that are not registered, and warn
+ about or even auto-register them. (svn externals make this quite
+ difficult!)
+
+* When there are chained mrconfig files, mr could be smarter about
+ checkouts and updates. Ie, when a new version of an mrconfig file is
+ checked out or updated, throw all the info from the old one away, and
+ process the new one.
+
+ Until this is fixed, checkouts and updates need to be manually repeated
+ after mrconfig files have changes. (See #447553)
--- /dev/null
+mr (1.00) unstable; urgency=low
+
+ In this version, mr has changed to not trust all mrconfig files by
+ default. Untrusted files are only allowed to run carefully checked commands.
+ Only the main ~/.mrconfig is now trusted by default. If you have other
+ mrconfig files, containing unusual commands, that mr should trust, you
+ will need to list them in ~/.mrtrust.
+
+ Also in this version, the -p flag is on by default, so mr will
+ look for a .mrconfig file in the current directory or one of its parent
+ directories, in addition to the main ~/.mrconfig file.
+
+ -- Joey Hess <joeyh@debian.org> Wed, 19 Jan 2011 13:40:16 -0400
--- /dev/null
+mr (1.16) UNRELEASED; urgency=low
+
+ * Add make install rule.
+ Thanks, v4hn
+
+ -- Joey Hess <joeyh@debian.org> Tue, 18 Jun 2013 20:16:25 -0400
+
+mr (1.15) unstable; urgency=low
+
+ * Added lib/repo, for support for repo (as used in Android)
+ Closes: #705652 Thanks, Peter Eisentraut
+ * Better cvs status.
+ Closes: #694037 Thanks, Paul Wise
+
+ -- Joey Hess <joeyh@debian.org> Sat, 04 May 2013 23:47:50 -0400
+
+mr (1.14) unstable; urgency=low
+
+ * Added a fetch command. Closes: #480580
+ * status: Now includes information about unpushed changes,
+ for git, git-svn, hg, and bzr. Closes: #693021
+ * Added lib/vis, an add-on to visualise repo history.
+ Closes: #693022 Thanks, Paul Wise
+ * Drop an extra -m from various commit/record commands.
+ Closes: #695478 Thanks, Paul Wise
+
+ -- Joey Hess <joeyh@debian.org> Wed, 13 Feb 2013 14:48:34 -0400
+
+mr (1.13) unstable; urgency=low
+
+ * Pass -q to cvs diff and update. Closes: #673367
+ * mr bootstrap: Now supports ssh:// urls.
+ * Simpler vcsh status command line. Closes: #685089
+ * Add grep subcommand. Uses ack-grep on VCS that do not have their own.
+ Closes: #685122
+
+ -- Joey Hess <joeyh@debian.org> Sat, 25 Aug 2012 11:14:41 -0400
+
+mr (1.12) unstable; urgency=low
+
+ * Ignore nonzero exit status of hg pull, which can happen
+ when there were no changes to pull. Closes: #661870
+ * Add vcsh to Suggests.
+ * Recognize a repo with a .git file as a git repo.
+ * Improve bzr register's heuristics to determine upstream repo.
+ Closes: #672843
+
+ -- Joey Hess <joeyh@debian.org> Mon, 14 May 2012 10:42:52 -0400
+
+mr (1.11) unstable; urgency=low
+
+ * Now supports the veracity vcs. Thanks, Jimmy Tang.
+ * vcsh is now supported without including a plugin.
+ * After checkout, run fixups chdired to the just checked out directory.
+
+ -- Joey Hess <joeyh@debian.org> Tue, 14 Feb 2012 18:13:29 -0400
+
+mr (1.10) unstable; urgency=low
+
+ * Fix display of trust errors.
+
+ -- Joey Hess <joeyh@debian.org> Tue, 27 Dec 2011 12:13:15 -0400
+
+mr (1.09) unstable; urgency=low
+
+ * Remove dir_test hack and add a way for vcs tests to run perl code,
+ using this for the same optimisation. Fixes support for git-svn
+ etc. Closes: #652317
+
+ -- Joey Hess <joeyh@debian.org> Fri, 16 Dec 2011 13:29:40 -0400
+
+mr (1.08) unstable; urgency=low
+
+ * Fix vcs test code. Closes: #651976
+
+ -- Joey Hess <joeyh@debian.org> Tue, 13 Dec 2011 16:21:19 -0400
+
+mr (1.07) unstable; urgency=low
+
+ * Added support for vcsh, enable with: include = cat /usr/share/mr/vcsh
+ Thanks, Richard Hartmann
+ * Block tty control codes in untrusted mr config files.
+ * Correct printing of line numbers when includes are used. Closes: #650952
+ * The previous fix for chaining to absolute paths broke chaining to relative
+ paths with more than one path segment. Thanks, Adam Spiers
+ * Support _append to add on to the existing value of a parameter.
+ Thanks, Adam Spiers
+ * Optimizations. Commands like "mr list" run up to 5 times faster.
+ * Fix shell escaping of parameters passed to mr commands. Closes: #644672
+ * Added --force option that disables repository skipping.
+ * Repositories using skip = lazy will not be checked out by "mr update"
+ or "mr checkout" unless --force is used.
+
+ -- Joey Hess <joeyh@debian.org> Mon, 12 Dec 2011 15:52:16 -0400
+
+mr (1.06) unstable; urgency=low
+
+ * Fix propigation of failure from pre and post hooks and from fixups.
+ * Support chaining to absolute paths.
+ * Add support for skip = lazy, a mode where mr only operates on repositories
+ that are checked out.
+
+ -- Joey Hess <joeyh@debian.org> Fri, 04 Nov 2011 17:03:17 -0400
+
+mr (1.05) unstable; urgency=low
+
+ * README now gives a quick into to using mr.
+ * Brought back the "deleted" parameter, which provides an easy way to
+ mark repositories that should be removed.
+ * Allow untrusted mrconfig files to set parameters to true/false.
+ So skip=true or deleted=true can be used in an untrusted mrconfig file.
+ * Also allow order=N in an untrusted mrconfig file.
+ * Support bzr checkouts, which are updated with "bzr update",
+ and to which bzr automatically pushes commits. Closes: #643589
+ * Use bzr branch, not deprecated bzr clone when registering bzr
+ repositories. Closes: #643591
+ * Allow bzr branch|clone|get|checkout in untrusted mrconfig files.
+ * Avoid using sed -r in git-fake-bare, for OSX portability.
+ * git-fake-bare: handle fake bare repositories with core.bare not set
+ (Thanks, Julien Rebetez)
+
+ -- Joey Hess <joeyh@debian.org> Tue, 27 Sep 2011 17:28:35 -0400
+
+mr (1.04) unstable; urgency=low
+
+ * Improve trust errors displayed while bootstrapping. Closes: #628234
+ * Allow mr register to be used with mrconfig file that does not yet
+ exist. Closes: #629217
+
+ -- Joey Hess <joeyh@debian.org> Sun, 19 Jun 2011 15:52:42 -0400
+
+mr (1.03) unstable; urgency=low
+
+ * Added git-subtree library. Thanks, Svend Sorensen
+ * Now --quiet supresses all output from commands run, as well
+ as from mr, unless a command fails. Closes: #529296
+
+ -- Joey Hess <joeyh@debian.org> Sun, 01 May 2011 19:09:35 -0400
+
+mr (1.02) unstable; urgency=low
+
+ * Fix bug in escaping.
+
+ -- Joey Hess <joeyh@debian.org> Sat, 22 Jan 2011 01:18:23 -0400
+
+mr (1.01) unstable; urgency=low
+
+ * Add "mr run", which can run an arbitrary command in each repository.
+
+ -- Joey Hess <joeyh@debian.org> Thu, 20 Jan 2011 16:50:49 -0400
+
+mr (1.00) unstable; urgency=low
+
+ * Trust flag day. All mrconfig files except the main ~/.mrconfig are
+ now untrusted by default, until listed in ~/.mrtrust.
+ * The -p flag is now enabled by default. mr first reads ~/.mrconfig,
+ and then looks for an additional .mrconfig file in the current
+ directory or one of its parent directories. Closes: #557963
+
+ -- Joey Hess <joeyh@debian.org> Wed, 19 Jan 2011 14:28:38 -0400
+
+mr (0.51) unstable; urgency=low
+
+ * Fix display when absolute directories are configured in mrconfig.
+ * Add push to manpage synopsis. Closes: #603029
+ * Do not return a nonzero exit status when all repositories were skipped.
+ Closes: #607287
+
+ -- Joey Hess <joeyh@debian.org> Thu, 16 Dec 2010 12:53:32 -0400
+
+mr (0.50) unstable; urgency=low
+
+ * Now supports the Fossil VCS. (Thanks, Jimmy Tang)
+ * Added fixups hook, which can be used to run a command after
+ a repository is checked out or updated. Closes: #590868
+ * Added support for arbitrary pre and post hooks for all defined mr
+ commands. For example, pre_commit is run before all commits; post_update
+ is run after all updates. Closes: #481341
+
+ -- Joey Hess <joeyh@debian.org> Sun, 29 Aug 2010 15:00:01 -0400
+
+mr (0.49) unstable; urgency=low
+
+ * Update suggests for git-core to git transition.
+ * Use short mode status output for git and bzr.
+ * Typo. Closes: #586233, #588913
+
+ -- Joey Hess <joeyh@debian.org> Sun, 18 Jul 2010 23:08:06 -0400
+
+mr (0.48) unstable; urgency=low
+
+ * Fix the hours_since function built into mr's shell library to
+ not exit, but return a true/false exit status. This allows it to be
+ used outside of skip tests.
+
+ -- Joey Hess <joeyh@debian.org> Fri, 05 Feb 2010 17:12:01 -0500
+
+mr (0.47) unstable; urgency=low
+
+ * Pass -L to curl to allow it to follow redirects when bootstrapping.
+ (Pavel Avgustinov)
+ * Allow empty sections to be used in mrconfig files.
+
+ -- Joey Hess <joeyh@debian.org> Fri, 05 Feb 2010 16:40:29 -0500
+
+mr (0.46) unstable; urgency=low
+
+ * Avoid using abs_path to determine canonical repo path, as that fails
+ when the repo has not yet been checked out.
+
+ -- Joey Hess <joeyh@debian.org> Thu, 10 Dec 2009 17:28:26 -0500
+
+mr (0.45) unstable; urgency=low
+
+ * Fix pod error. Closes: #558012
+ * bootstrap: Improve stats display.
+ * Add --insecure option that can be used to ignore SSL cert errors
+ when bootstrapping. (Pavel Avgustinov)
+ * Fix handling of a repository in "."
+
+ -- Joey Hess <joeyh@debian.org> Tue, 08 Dec 2009 14:58:21 -0500
+
+mr (0.44) unstable; urgency=low
+
+ * Make mr -i pass -i to the shell, to ensure it knows it should be an
+ interactive shell.
+ * mr help: Support man on SunOS and OS X. (Geoff Davis)
+ * mr bootstrap: Fix cross-device rename failure. Closes: #557962
+
+ -- Joey Hess <joeyh@debian.org> Wed, 25 Nov 2009 12:39:08 -0500
+
+mr (0.43) unstable; urgency=low
+
+ * Set User-Agent to "mr" when downloading the mrconfig file
+ with mr bootstrap. Closes: #541778
+ * Man page typo. Closes: #545412
+ * mr bootstrap: Allow a directory to checkout into to be specified,
+ and create it if necessary.
+ * mr bootstrap: If the mrconfig file contains a repository named ".",
+ check it out into the top level of the directory bootstrapped.
+
+ -- Joey Hess <joeyh@debian.org> Tue, 08 Sep 2009 20:22:32 -0400
+
+mr (0.42) unstable; urgency=low
+
+ * Add support for ~/.mrtrust, which can be used to list trusted mrconfig
+ files. If you create this file, all files not listed in it will be treated
+ as untrusted, and carefully limited in what they can do. This improves
+ security when using mrconfig files provided by others.
+
+ -- Joey Hess <joeyh@debian.org> Fri, 07 Aug 2009 16:38:31 -0400
+
+mr (0.41) unstable; urgency=low
+
+ * Add -p switch, that makes mr search the current directory and its
+ parents for a .mrconfig file to use.
+ * Add `mr bootstrap`, which downloads an url to a .mrconfig file
+ in the current directory and then checks out all repositories configured
+ in it. This is intended for projects that want to publish a mrconfig file
+ to automate use of their several repositories.
+
+ -- Joey Hess <joeyh@debian.org> Thu, 06 Aug 2009 22:38:58 -0400
+
+mr (0.40) unstable; urgency=low
+
+ * Pass --pull to bzr merge to avoid needing to commit
+ merged changes. Closes: #524352
+
+ -- Joey Hess <joeyh@debian.org> Fri, 31 Jul 2009 13:47:22 -0400
+
+mr (0.39) unstable; urgency=low
+
+ * Determine current branch for fake-bare update (martin f. krafft)
+
+ -- Joey Hess <joeyh@debian.org> Mon, 09 Mar 2009 22:50:54 -0400
+
+mr (0.38) unstable; urgency=low
+
+ * Remove gitless lib. It turned out to be better to simply
+ use git clone --shared when checking out. Sorry for the churn..
+
+ -- Joey Hess <joeyh@debian.org> Thu, 22 Jan 2009 15:39:33 -0500
+
+mr (0.37) unstable; urgency=low
+
+ * Add "gitless" file to lib.
+ This adds a special type of git repository, where the .git directory
+ is stored on a file server, to avoid wasting space with it on the client.
+ * Revert buggy change to directory printing code.
+
+ -- Joey Hess <joeyh@debian.org> Wed, 21 Jan 2009 01:40:59 -0500
+
+mr (0.36) unstable; urgency=low
+
+ * Add webcheckout command. See <http://kitenet.net/~joey/rfc/rel-vcs/>
+ * Fix display of repos configured at absolute path locations.
+
+ -- Joey Hess <joeyh@debian.org> Sat, 10 Jan 2009 16:42:07 -0500
+
+mr (0.35) unstable; urgency=low
+
+ * Warn if an include command fails nonzero. Closes: #495306
+ * Remove stray character in pod that uglified man page. Closes: #495731
+ * Create ~/.mrlog not world readable.
+ * Pass additional options to darcs push when pushing. Closes: #495734
+
+ -- Joey Hess <joeyh@debian.org> Thu, 16 Oct 2008 14:51:00 -0400
+
+mr (0.34) unstable; urgency=low
+
+ * Fix bug when remembering failed commands in offline mode.
+
+ -- Joey Hess <joeyh@debian.org> Sat, 02 Aug 2008 12:27:21 -0400
+
+mr (0.33) unstable; urgency=low
+
+ * Add a push subcommand, which pushes committed changes for DCVS, and
+ does nothing for svn/cvs. Closes: #491865
+
+ -- Joey Hess <joeyh@debian.org> Tue, 22 Jul 2008 15:22:01 -0400
+
+mr (0.32) unstable; urgency=low
+
+ * Include the right (v2) version of GPL in the source.
+
+ -- Joey Hess <joeyh@debian.org> Tue, 22 Jul 2008 00:28:55 -0400
+
+mr (0.31) unstable; urgency=low
+
+ * Add an offline mode. When in offline mode, mr will remember commands
+ that failed (presumably due to lack of network access), and will run
+ them again once back online. (This is for all the laptop users
+ like me who don't want to have to remember which git repos to mr push
+ when they come back from a nice long hack in the woods. :-)
+
+ -- Joey Hess <joeyh@debian.org> Fri, 27 Jun 2008 23:19:23 -0400
+
+mr (0.30) unstable; urgency=low
+
+ [ martin f. krafft ]
+ * Several git-fake-bare improvements.
+
+ [ Joey Hess ]
+ * Fix mr -c <config> register. (Thanks, Daniel Bungert)
+
+ -- Joey Hess <joeyh@debian.org> Wed, 25 Jun 2008 13:38:31 -0400
+
+mr (0.29) unstable; urgency=low
+
+ * Allow environment variables in section headers. Pass such headers
+ through the shell to expand them. Closes: #480421
+ * For the checkout command, MR_REPO was set to the name of the parent
+ directory of the directory to checkout. It is supposed to be the
+ path to the directory itself though; fixed this. Closes: #480424
+
+ -- Joey Hess <joeyh@debian.org> Wed, 14 May 2008 19:38:18 -0400
+
+mr (0.28) unstable; urgency=low
+
+ * Use debhelper v7, rules file minimisation.
+ * Add a Makefile.
+
+ -- Joey Hess <joeyh@debian.org> Wed, 23 Apr 2008 21:56:06 -0400
+
+mr (0.27) unstable; urgency=low
+
+ * Ignore exit code from darcs whatsnew, which can be nonzero if there
+ are no changes. Closes: #476650
+
+ -- Joey Hess <joeyh@debian.org> Sat, 19 Apr 2008 16:47:04 -0400
+
+mr (0.26) unstable; urgency=low
+
+ * Add -i option to start a shell if an operation fails. Closes: #474962
+
+ -- Joey Hess <joeyh@debian.org> Tue, 08 Apr 2008 20:58:44 -0400
+
+mr (0.25) unstable; urgency=low
+
+ * git-fake-bare: Add support for diff, commit, and record. (madduck)
+
+ -- Joey Hess <joeyh@debian.org> Mon, 10 Mar 2008 15:56:17 -0400
+
+mr (0.24) unstable; urgency=low
+
+ * Correct GIT_CONFIG settings in git-svn. Closes: #463941
+
+ -- Joey Hess <joeyh@debian.org> Wed, 06 Feb 2008 13:04:30 -0500
+
+mr (0.23) unstable; urgency=low
+
+ * Fix tests in git-svn include file to use MR_REPO. Closes: #463941
+
+ -- Joey Hess <joeyh@debian.org> Tue, 05 Feb 2008 13:26:05 -0500
+
+mr (0.22) unstable; urgency=low
+
+ * Update bzr registration code to work with current version of bzr,
+ using bzr info to get the url. Closes: #463867
+
+ -- Joey Hess <joeyh@debian.org> Sun, 03 Feb 2008 17:04:59 -0500
+
+mr (0.21) unstable; urgency=low
+
+ * Add a git-svn file, which can be included to add support for git-svn
+ repositories. Thanks to Clifford W. Hansen and Bastian Kleineidam
+ for implementations. Closes: #462408
+
+ -- Joey Hess <joeyh@debian.org> Thu, 24 Jan 2008 13:45:05 -0500
+
+mr (0.20) unstable; urgency=low
+
+ * Add -q flag.
+ * darcs: Add -u to diff to get a more usual unified diff.
+ * Add a "record" subcommand, borrowing termonology from darcs. This does a
+ local commit, but does not push changes to remote repos.
+ * Improve unison support.
+
+ -- Joey Hess <joeyh@debian.org> Wed, 02 Jan 2008 22:49:53 -0500
+
+mr (0.19) unstable; urgency=low
+
+ * Support versions of man that don't use -l.
+ * Updating git repos no longer uses git-pull -t by default. Git makes it
+ to much of a PITA to do this, since -t makes git-pull require the
+ repository and refspec be specified at the command line (which is a bug in
+ git (#456035). mr used to hardcode those to "origin" and "master", but
+ that's not always the right choice. So give up on forcing git to be sane
+ about pulling down all tags. It's insane. Live with it, or configure your
+ own update command.
+
+ -- Joey Hess <joeyh@debian.org> Wed, 12 Dec 2007 21:30:45 -0500
+
+mr (0.18) unstable; urgency=low
+
+ * darcs: Use record subcommand, there is no commit subcommand. Closes: #453501
+ * darcs: De-p register code. Closes: #453502
+
+ -- Joey Hess <joeyh@debian.org> Thu, 29 Nov 2007 17:48:09 -0500
+
+mr (0.17) unstable; urgency=low
+
+ * Even bigger hammer: Set LC_ALL. Closes: #453305
+
+ -- Joey Hess <joeyh@debian.org> Thu, 29 Nov 2007 11:18:40 -0500
+
+mr (0.16) unstable; urgency=low
+
+ * Use LC_MESSAGES=C not LANG=C, as if the user has LC_MESSAGES set, LANG
+ won't override it. Closes: #453305
+
+ -- Joey Hess <joeyh@debian.org> Wed, 28 Nov 2007 15:46:38 -0500
+
+mr (0.15) unstable; urgency=low
+
+ [ Joey Hess ]
+ * Allow -n to be passed a number to specify how deep to go into
+ subdirectories to find repositories to act on. Closes: #450621
+
+ [ Josh Triplett ]
+ * Make -j with no argument run unlimited jobs in parallel, like make -j
+ Closes: #452467
+
+ [ Joey Hess ]
+ * Add a warning about runing too many jobs at a time.
+
+ -- Joey Hess <joeyh@debian.org> Sun, 25 Nov 2007 13:13:47 -0500
+
+mr (0.14) unstable; urgency=low
+
+ * Portability fixes for the non-POSIX shell of SunOS 5. Patch from Ken
+ Bloom. Closes: #449592
+ * Cut number of rcs tests run in half.
+
+ -- Joey Hess <joeyh@debian.org> Sun, 11 Nov 2007 01:10:18 -0500
+
+mr (0.13) unstable; urgency=low
+
+ * -c was broken, fix. Closes: #449539
+
+ -- Joey Hess <joeyh@debian.org> Tue, 06 Nov 2007 11:39:04 -0500
+
+mr (0.12) unstable; urgency=low
+
+ * Avoid creating parent directory for a checkout that will ultimately be
+ skipped.
+ * Don't try to test the repo type when doing a checkout, that can't work
+ since the repo isn't there yet. It was actually checking the repo type
+ of the parent directory, which caused several unexpected behaviors.
+
+ -- Joey Hess <joeyh@debian.org> Sat, 03 Nov 2007 14:22:00 -0400
+
+mr (0.11) unstable; urgency=low
+
+ * Avoid using commands like git-config and instead use "git config".
+ In some configurations, only the main git command is in the path.
+ * Allow option bundling, mostly so "-j2" will work.
+ * Better error message if more than one repo type test matches a single
+ directory.
+
+ -- Joey Hess <joeyh@debian.org> Fri, 02 Nov 2007 23:28:52 -0400
+
+mr (0.10) unstable; urgency=low
+
+ * Add a lib file for using unison with mr.
+ * Make registration work when no config file yet exists. Closes: #448422
+
+ -- Joey Hess <joeyh@debian.org> Sun, 28 Oct 2007 19:55:03 -0400
+
+mr (0.9) unstable; urgency=low
+
+ * Split up actions, so each rcs has its own set of action commands,
+ and mr tests to see what rcs is used by a repository, and dispatches
+ the command.
+
+ This will make it much easier to add new rcses, or modify just the
+ command that mr uses for one command for one rcs, without duplicating a
+ lot of code, and without needing to modify mr at all.
+
+ The old style unsplit actions are still supported, and are what most
+ mrconfig files will still use; this change is fully backwards compatible.
+ * Changed some things in the enviroment for the register action.
+ It's now run in the directory that the user specifies, and MR_REPO
+ is set to contain the basename of the directory that the checkout
+ command should check out. These changes should be backware compatible
+ to existing register actions.
+ * Add info and warning functions to the shell library. (madduck)
+ * Add git_bare and git_fake_bare rcs types to handle
+ bare and fake bare (non-bare with a detached worktree) git repositories.
+ (Diff and commit do not work yet to fake bare repos). (madduck)
+ * Add a vim modeline to preserve joey's tabbing prefs. (madduck)
+ * Add support for including one mrconfig file from another. Unlike chaining,
+ this doesn't change the paths, and is not tied to a particular
+ subdirectory. It's useful for loading up library mrconfig files.
+ * Split git fake-bare support out into a lib/git-fake-bare.
+ Partly because it's a good example of how to add a new revision control
+ type and use includes, and partly because it's currently too ugly to be
+ in mr itself due to bugs and limitations in git.
+
+ -- Joey Hess <joeyh@debian.org> Fri, 26 Oct 2007 03:00:40 -0400
+
+mr (0.8) unstable; urgency=low
+
+ * Improve "in subdir" message.
+ * Patch from Simon McVittie, to pass -a to darcs commands to avoid
+ interactive updates. Closes: #447999
+
+ -- Joey Hess <joeyh@debian.org> Thu, 25 Oct 2007 06:22:45 -0400
+
+mr (0.7) unstable; urgency=low
+
+ * Fix inverted tests in tags command. (madduck)
+ * Patch from Simon McVittie, adding support for darcs repositories.
+ Closes: #447729
+
+ -- Joey Hess <joeyh@debian.org> Wed, 24 Oct 2007 01:18:27 -0400
+
+mr (0.6) unstable; urgency=low
+
+ * Add to the example mrconfig a "tags" command that lists tags.
+ (Currently only for svn and git.)
+ * hours_since was broken by design, and fixing it involved changing
+ its calling convention. If you used the old version, the new version
+ will error out.
+ * Add ability to reorder repos, if you want mr to act on a given repo first
+ or last.
+
+ -- Joey Hess <joeyh@debian.org> Sun, 21 Oct 2007 21:53:28 -0400
+
+mr (0.5) unstable; urgency=low
+
+ [ Joey Hess ]
+ * Removed special case repository deletion handling code. The same
+ thing can be accomplished in a mrconfig by skipping a repo unless
+ it exists, and printing a reminder on update. See the mrconfig file
+ for an example.
+ * Fix output of "mr config DEFAULT lib".
+ * Change mr update to use git pull -t origin master, to make sure new tags
+ are pulled. And since those might not always be the right parameters
+ for git pull, any parameters passed to mr update will replace them.
+
+ [ Alexander Wirt ]
+ * Add support for mercurial.
+
+ [ Joey Hess ]
+ * Incorporate code based on Anthony Towns's mrs, to allow running
+ multiple jobs in parallel. The -j flag controls this.
+ This can produce enormous speedups. For me, mr update takes
+ 12 minutes, while mr -j 10 update takes 1 minute!
+
+ -- Joey Hess <joeyh@debian.org> Sun, 21 Oct 2007 01:27:23 -0400
+
+mr (0.4) unstable; urgency=low
+
+ * Fix mr register of a subdir to also work with -c.
+ * Signal handling for commands run by mr, including handling of SIGINT
+ to stop mr.
+ * Ensure parent dirs exist prior to checkout. (madduck)
+ * Output list of failed repos to stderr when -s is used.
+ * Fix a bug caused by a stupid typo.
+ * Add the -n switch, for disabling recursion.
+ * Allow for more complex deleted tests, such as marking a repo deleted on
+ some hosts, while not on others.
+ * Support registering CVS repositories. Closes: #447171
+ * MR_CONFIG now points to the config file that contains the repo mr is
+ acting on.
+ * Make mr register smarter about what mrconfig file to write to.
+ (It probably does what you'd expect it to do now; if it doesn't, -c
+ can still override.)
+
+ -- Joey Hess <joeyh@debian.org> Thu, 18 Oct 2007 16:20:34 -0400
+
+mr (0.3) unstable; urgency=low
+
+ * Add a check to make sure the expected directory exists after checkout.
+ * mr register will now write to whatever config file is specified with -c
+ * Typo fixes from madduck.
+
+ -- Joey Hess <joeyh@debian.org> Wed, 17 Oct 2007 12:48:55 -0400
+
+mr (0.2) unstable; urgency=low
+
+ * Fix line number display for config file parse errors.
+ * Fix a bug in inheritance of default settings in chained .mrconfig files.
+ * Add the -s option.
+
+ -- Joey Hess <joeyh@debian.org> Tue, 16 Oct 2007 02:39:08 -0400
+
+mr (0.1) unstable; urgency=low
+
+ * First release.
+
+ -- Joey Hess <joeyh@debian.org> Sun, 14 Oct 2007 14:14:40 -0400
--- /dev/null
+Source: mr
+Section: vcs
+Priority: optional
+Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.9.0)
+Maintainer: Joey Hess <joeyh@debian.org>
+Standards-Version: 3.9.3
+Homepage: http://joeyh.name/code/mr/
+Vcs-Git: git://git.kitenet.net/mr
+
+Package: mr
+Architecture: all
+Section: vcs
+Depends: ${misc:Depends}
+Suggests: subversion, git-core | git (>= 1:1.7), cvs, bzr, mercurial, darcs, fossil, vcsh, liburi-perl, curl
+Recommends: libwww-perl, libhtml-parser-perl, perl
+Description: Multiple Repository management tool
+ The mr(1) command can checkout, update, or perform other actions on
+ a set of repositories as if they were one combined respository. It
+ supports any combination of git, svn, mercurial, bzr, darcs, cvs, vcsh,
+ fossil, and veracity repositories, and support for other version control
+ systems can easily be added. (There are extensions adding support for unison
+ and git-svn, among others.)
+ .
+ It is extremely configurable via simple shell scripting. Some examples
+ of things it can do include:
+ .
+ * Update a repository no more frequently than once every twelve hours.
+ * Run an arbitrary command before committing to a repository.
+ * When updating a git repository, pull from two different upstreams
+ and merge the two together.
+ * Run several repository updates in parallel, greatly speeding up
+ the update process.
+ * Remember actions that failed due to a laptop being offline, so they
+ can be retried when it comes back online.
+ .
+ This package also includes the webcheckout command.
--- /dev/null
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+
+Files: *
+Copyright: (c) 2007-2011 Joey Hess <joeyh@debian.org>
+License: GPL-2+
+ On Debian systems, the complete text of the GPL can be found in
+ /usr/share/common-licenses/GPL.
--- /dev/null
+README TODO
--- /dev/null
+mrconfig mrconfig.complex
--- /dev/null
+#!/usr/bin/make -f
+%:
+ dh $@
+
+# Not intended for use by anyone except the author.
+announcedir:
+ @echo ${HOME}/src/joeywiki/code/mr/news
--- /dev/null
+# An example of how to add a new version control system type to mr.
+# git fake bare repositories have a detached workspace. One potential
+# application is storing dotfiles in git, keeping them checked out in
+# ones $HOME, but checked into different git repositories. This file adds
+# support for them, separate from the normal git support.
+
+# To make mr use this file, add a line like this inside the [DEFAULT]
+# section of your ~/.mrconfig
+#include = cat /usr/share/mr/git-fake-bare
+
+# And an example repo using it would look something like:
+#[.dotfiles]
+#checkout = git_fake_bare_checkout git://... .dotfiles <worktree, eg. ..>
+
+lib =
+ # git doesn't have an easy way to check out such a repo, so
+ # do it by hand
+ git_fake_bare_checkout() {
+ local url; url="$1"
+ local repo; repo="$2"
+ local worktree; worktree="$3"
+ git clone --no-checkout "$url" "$repo"
+ cd "$repo"
+ mkdir -p "$worktree"
+ PWD="`pwd`"
+ mv .git/* .
+ rmdir .git
+ GIT_DIR="$PWD" git read-tree HEAD
+ GIT_DIR="$PWD" git checkout-index -a --prefix="$worktree" || true
+ GIT_DIR="$PWD" git config core.worktree "$worktree"
+ }
+ git_get_worktree() {
+ local worktree
+ worktree="$(git config --get core.worktree)" || true
+ if [ -z "$worktree" ]; then
+ error "git worktree is not set"
+ fi
+ worktree="${worktree%%/}/"
+ if [ ! -d "$worktree" ]; then
+ error "git worktree $worktree does not exist"
+ fi
+ echo "$worktree"
+ }
+
+git_fake_bare_test = perl:
+ -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" &&
+ -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" &&
+ `GIT_DIR="$ENV{MR_REPO}" git config --get core.worktree` ne ""
+
+git_fake_bare_update =
+ args="$@"
+ branch="$(GIT_DIR="$MR_REPO" git symbolic-ref HEAD | sed -e 's,.*/,,')"
+ [ -z "$args" ] && args="-t origin $branch"
+ GIT_DIR="$MR_REPO" git pull $args
+
+git_fake_bare_status = GIT_DIR="$MR_REPO" git status -s "$@" || true
+
+git_fake_bare_commit =
+ cd "$(git_get_worktree)"
+ GIT_DIR="$MR_REPO" git commit -a "$@"
+ GIT_DIR="$MR_REPO" git push --all
+
+git_fake_bare_push =
+ GIT_DIR="$MR_REPO" git push --all
+
+git_fake_bare_record =
+ cd "$(git_get_worktree)"
+ GIT_DIR="$MR_REPO" git commit -a "$@"
+
+git_fake_bare_diff =
+ cd "$(git_get_worktree)"
+ GIT_DIR="$MR_REPO" git diff "$@"
+
+git_fake_bare_log = GIT_DIR="$MR_REPO" git log "$@"
+
+git_fake_bare_register =
+ url="$(LC_ALL=C GIT_CONFIG=config git config --get remote.origin.url)" || true
+ if [ -z "$url" ]; then
+ error "cannot determine git url"
+ fi
+ worktree="$(git_get_worktree)"
+ echo "Registering git url: $url in $MR_CONFIG (with worktree $worktree)"
+ mr -c "$MR_CONFIG" config "`pwd`" \
+ checkout="git_fake_bare_checkout '$url' '$MR_REPO' '$worktree'"
+
+# vim:sw=8:sts=0:ts=8:noet
--- /dev/null
+# Add support for Avery Pennarun's git-subtree
+#
+# To make mr use this file, add a line like this inside the [DEFAULT]
+# section of your ~/.mrconfig
+#include = cat /usr/share/mr/git-subtree
+#
+# Example:
+#
+# [repo]
+# checkout = git clone git.example.org:repo.git
+#
+# [repo/lib]
+# git_subtree_test = true
+# checkout = git subtree add --prefix=lib git.example.org:lib.git master
+# update = git_subtree_update --prefix=lib git.example.org:lib.git master
+# push = git_subtree_push --prefix=lib git.example.org:lib.git master
+
+lib =
+ git_get_toplevel() {
+ local toplevel
+ toplevel="$(git rev-parse --show-toplevel)" || true
+ if [ -z "$toplevel" ]; then
+ error "git toplevel is not set"
+ fi
+ toplevel="${toplevel%%/}/"
+ if [ ! -d "$toplevel" ]; then
+ error "git toplevel $toplevel does not exist"
+ fi
+ echo "$toplevel"
+ }
+ git_subtree_update() {
+ cd "$(git_get_toplevel)"
+ git subtree pull "$@"
+ }
+ git_subtree_push() {
+ cd "$(git_get_toplevel)"
+ git subtree push "$@"
+ }
+
+git_subtree_status = git status -s "$@" . || true
+git_subtree_commit = git add . && git commit "$@" && git_subtree_push
+git_subtree_record = git add . && git commit "$@"
+git_subtree_diff = git diff .
+git_subtree_log = git log .
--- /dev/null
+# Adds support for git-svn repositories.
+
+# To make mr use this file, add a line like this inside the [DEFAULT]
+# section of your ~/.mrconfig
+#include = cat /usr/share/mr/git-svn
+#
+# Note that by default this makes mr update do a git svn fetch.
+# Some might prefer it to do a git svn rebase, if you do, you can
+# configure that as follows in your ~/.mrconfig:
+#git_svn_update = git svn rebase
+git_svn_update = git svn fetch
+git_svn_status = git status -s "$@" || true; git --no-pager log --branches --not --remotes --simplify-by-decoration --decorate --oneline || true
+git_svn_commit = git svn dcommit
+git_svn_push = git svn dcommit
+git_svn_record = git commit -a "$@"
+git_svn_diff = git diff "$@"
+git_svn_log = git svn log "$@"
+
+git_test = perl:
+ -d "$ENV{MR_REPO}/.git" &&
+ `GIT_CONFIG="$ENV{MR_REPO}"/.git/config git config --get svn-remote.svn.url` eq ""
+
+git_svn_test = perl:
+ -d "$ENV{MR_REPO}/.git" &&
+ `GIT_CONFIG="$ENV{MR_REPO}"/.git/config git config --get svn-remote.svn.url` ne ""
+
+git_svn_register =
+ url="`LC_ALL=C git config --get svn-remote.svn.url`" || true
+ if [ -z "$url" ]; then
+ error "cannot determine git svn url"
+ fi
+ echo "Registering git svn url: $url in $MR_CONFIG"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="git svn clone '$url' '$MR_REPO'"
+
+# vim:sw=8:sts=0:ts=8:noet
--- /dev/null
+# Adds support for repo repositories.
+# http://source.android.com/source/version-control.html
+
+# To make mr use this file, add a line like this inside the [DEFAULT]
+# section of your ~/.mrconfig
+#include = cat /usr/share/mr/repo
+
+repo_test = perl: -d "$ENV{MR_REPO}/.repo"
+repo_diff = repo diff "$@"
+repo_grep = repo grep "$@"
+repo_status = repo status "$@"
+repo_update = repo sync "$@"
--- /dev/null
+# This allows using unison as a "version control system" with mr.
+#
+# You need to configure unison by setting up files in ~/.unison named
+# the same as the basenames of the directories you want to sync, and
+# containing unison configuration to sync them.
+#
+# By default commit will be interactive; you can set batch mode in the
+# config file to disable this. All other commands use batch mode by
+# default.
+
+# To make mr use this file, add a line like this inside the [DEFAULT]
+# section of your ~/.mrconfig
+#include = cat /usr/share/mr/unison
+
+# And an example repo using it would look something like:
+#[music]
+#unison_test = true
+#checkout = unison_checkout music
+
+lib =
+ # The name of the directory containing the repo is assumed to
+ # match that of a unison config file.
+ unison_config() {
+ basename "$MR_REPO"
+ }
+ unison_batch() {
+ unison -batch $(unison_config)
+ }
+ unison_checkout() {
+ mkdir "$1" && cd "$1" && unison -batch "$1"
+ }
+
+unison_update = unison_batch
+unison_push = unison_batch
+unison_commit = unison $(unison_config)
+
+# vim:sw=8:sts=0:ts=8:noet
--- /dev/null
+# To make mr use this file, add a line like this inside the [DEFAULT]
+# section of your ~/.mrconfig
+#include = cat /usr/share/mr/vcsh
+
+# But, that's pointless to do, since the vcsh support has been moved from
+# this plugin into mr.
+
+# And an example repo using it would look something like:
+#[$HOME/.config/vcsh/repo.d/zsh.git]
+#checkout = vcsh clone git://github.com/RichiH/zshrc.git zsh
--- /dev/null
+# Adds a "mr vis" command to visualise repository history.
+
+# To make mr use this file, add a line like this inside the [DEFAULT]
+# section of your ~/.mrconfig
+#include = cat /usr/share/mr/vis
+
+cvs_vis = cvs diff | $EDITOR -
+svn_vis = svn status | $EDITOR -
+git_svn_vis = gitk --all
+git_vis = gitk --all
+bzr_vis = bzr visualize
+hg_vis = hg view
--- /dev/null
+#!/usr/bin/perl
+
+=head1 NAME
+
+mr - a Multiple Repository management tool
+
+=head1 SYNOPSIS
+
+B<mr> [options] checkout
+
+B<mr> [options] update
+
+B<mr> [options] status
+
+B<mr> [options] commit [-m "message"]
+
+B<mr> [options] record [-m "message"]
+
+B<mr> [options] fetch
+
+B<mr> [options] push
+
+B<mr> [options] diff
+
+B<mr> [options] log
+
+B<mr> [options] run command [param ...]
+
+B<mr> [options] bootstrap url [directory]
+
+B<mr> [options] register [repository]
+
+B<mr> [options] config section ["parameter=[value]" ...]
+
+B<mr> [options] action [params ...]
+
+B<mr> [options] [online|offline]
+
+B<mr> [options] remember action [params ...]
+
+=head1 DESCRIPTION
+
+B<mr> is a Multiple Repository management tool. It can checkout, update, or
+perform other actions on a set of repositories as if they were one combined
+repository. It supports any combination of subversion, git, cvs, mercurial,
+bzr, darcs, fossil and veracity repositories, and support for other version
+control systems can easily be added.
+
+B<mr> cds into and operates on all registered repositories at or below your
+working directory. Or, if you are in a subdirectory of a repository that
+contains no other registered repositories, it will stay in that directory,
+and work on only that repository,
+
+B<mr> is configured by .mrconfig files, which list the repositories. It
+starts by reading the .mrconfig file in your home directory, and this can
+in turn chain load .mrconfig files from repositories. It also automatically
+looks for a .mrconfig file in the current directory, or in one of its
+parent directories.
+
+These predefined commands should be fairly familiar to users of any version
+control system:
+
+=over 4
+
+=item checkout (or co)
+
+Checks out any repositories that are not already checked out.
+
+=item update
+
+Updates each repository from its configured remote repository.
+
+If a repository isn't checked out yet, it will first check it out.
+
+=item status
+
+Displays a status report for each repository, showing what
+uncommitted changes are present in the repository. For distributed version
+control systems, also shows unpushed local branches.
+
+=item commit (or ci)
+
+Commits changes to each repository. (By default, changes are pushed to the
+remote repository too, when using distributed systems like git. If you
+don't like this default, you can change it in your .mrconfig, or use record
+instead.)
+
+The optional -m parameter allows specifying a commit message.
+
+=item record
+
+Records changes to the local repository, but does not push them to the
+remote repository. Only supported for distributed version control systems.
+
+The optional -m parameter allows specifying a commit message.
+
+=item fetch
+
+Fetches from each repository's remote repository, but does not
+update the working copy. Only supported for some distributed version
+control systems.
+
+=item push
+
+Pushes committed local changes to the remote repository. A no-op for
+centralized version control systems.
+
+=item diff
+
+Show a diff of uncommitted changes.
+
+=item log
+
+Show the commit log.
+
+=item run command [param ...]
+
+Runs the specified command in each repository.
+
+=back
+
+These commands are also available:
+
+=over 4
+
+=item bootstrap url [directory]
+
+Causes mr to download the url, and use it as a .mrconfig file to checkout
+the repositories listed in it, into the specified directory.
+
+To use scp to download, the url may have the form ssh://[user@]host:file
+
+The directory will be created if it does not exist. If no directory is
+specified, the current directory will be used.
+
+If the .mrconfig file includes a repository named ".", that
+is checked out into the top of the specified directory.
+
+=item list (or ls)
+
+List the repositories that mr will act on.
+
+=item register
+
+Register an existing repository in a mrconfig file. By default, the
+repository in the current directory is registered, or you can specify a
+directory to register.
+
+The mrconfig file that is modified is chosen by either the -c option, or by
+looking for the closest known one at or in a parent of the current directory.
+
+=item config
+
+Adds, modifies, removes, or prints a value from a mrconfig file. The next
+parameter is the name of the section the value is in. To add or modify
+values, use one or more instances of "parameter=value". Use "parameter=" to
+remove a parameter. Use just "parameter" to get the value of a parameter.
+
+For example, to add (or edit) a repository in src/foo:
+
+ mr config src/foo checkout="svn co svn://example.com/foo/trunk foo"
+
+To show the command that mr uses to update the repository in src/foo:
+
+ mr config src/foo update
+
+To see the built-in library of shell functions contained in mr:
+
+ mr config DEFAULT lib
+
+The mrconfig file that is used is chosen by either the -c option, or by
+looking for the closest known one at or in a parent of the current directory.
+
+=item offline
+
+Advises mr that it is in offline mode. Any commands that fail in
+offline mode will be remembered, and retried when mr is told it's online.
+
+=item online
+
+Advices mr that it is in online mode again. Commands that failed while in
+offline mode will be re-run.
+
+=item remember
+
+Remember a command, to be run later when mr re-enters online mode. This
+implicitly puts mr into offline mode. The command can be any regular mr
+command. This is useful when you know that a command will fail due to being
+offline, and so don't want to run it right now at all, but just remember
+to run it when you go back online.
+
+=item help
+
+Displays this help.
+
+=back
+
+Actions can be abbreviated to any unambiguous substring, so
+"mr st" is equivalent to "mr status", and "mr up" is equivalent to "mr
+update"
+
+Additional parameters can be passed to most commands, and are passed on
+unchanged to the underlying version control system. This is mostly useful
+if the repositories mr will act on all use the same version control
+system.
+
+=head1 OPTIONS
+
+=over 4
+
+=item -d directory
+
+=item --directory directory
+
+Specifies the topmost directory that B<mr> should work in. The default is
+the current working directory.
+
+=item -c mrconfig
+
+=item --config mrconfig
+
+Use the specified mrconfig file. The default is to use both F<~/.mrconfig>
+as well as look for a F<.mrconfig> file in the current directory, or in one
+of its parent directories.
+
+=item -f
+
+=item --force
+
+Force mr to act on repositories that would normally be skipped due to their
+configuration.
+
+=item -v
+
+=item --verbose
+
+Be verbose.
+
+=item -q
+
+=item --quiet
+
+Be quiet. This suppresses mr's usual output, as well as any output from
+commands that are run (including stderr output). If a command fails,
+the output will be shown.
+
+=item -k
+
+=item --insecure
+
+Accept untrusted SSL certificates when bootstrapping.
+
+=item -s
+
+=item --stats
+
+Expand the statistics line displayed at the end to include information
+about exactly which repositories failed and were skipped, if any.
+
+=item -i
+
+=item --interactive
+
+Interactive mode. If a repository fails to be processed, a subshell will be
+started which you can use to resolve or investigate the problem. Exit the
+subshell to continue the mr run.
+
+=item -n [number]
+
+=item --no-recurse [number]
+
+If no number if specified, just operate on the repository for the current
+directory, do not recurse into deeper repositories.
+
+If a number is specified, will recurse into repositories at most that many
+subdirectories deep. For example, with -n 2 it would recurse into ./src/foo,
+but not ./src/packages/bar.
+
+=item -j [number]
+
+=item --jobs [number]
+
+Run the specified number of jobs in parallel, or an unlimited number of jobs
+with no number specified. This can greatly speed up operations such as updates.
+It is not recommended for interactive operations.
+
+Note that running more than 10 jobs at a time is likely to run afoul of
+ssh connection limits. Running between 3 and 5 jobs at a time will yield
+a good speedup in updates without loading the machine too much.
+
+=item -t
+
+=item --trust-all
+
+Trust all mrconfig files even if they are not listed in F<~/.mrtrust>.
+Use with caution.
+
+=item -p
+
+=item --path
+
+This obsolete flag is ignored.
+
+=back
+
+=head1 MRCONFIG FILES
+
+Here is an example F<.mrconfig> file:
+
+ [src]
+ checkout = svn checkout svn://svn.example.com/src/trunk src
+ chain = true
+
+ [src/linux-2.6]
+ checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git &&
+ cd linux-2.6 &&
+ git checkout -b mybranch origin/master
+
+The F<.mrconfig> file uses a variant of the INI file format. Lines
+starting with "#" are comments. Values can be continued to the
+following line by indenting the line with whitespace.
+
+The C<DEFAULT> section allows setting default values for the sections that
+come after it.
+
+The C<ALIAS> section allows adding aliases for actions. Each parameter
+is an alias, and its value is the action to use.
+
+All other sections add repositories. The section header specifies the
+directory where the repository is located. This is relative to the directory
+that contains the mrconfig file, but you can also choose to use absolute
+paths. (Note that you can use environment variables in section names; they
+will be passed through the shell for expansion. For example,
+C<[$HOSTNAME]>, or C<[${HOSTNAME}foo]>).
+
+Within a section, each parameter defines a shell command to run to handle a
+given action. mr contains default handlers for "update", "status",
+"commit", and other standard actions.
+
+Normally you only need to specify what to do for "checkout". Here you
+specify the command to run in order to create a checkout of the repository.
+The command will be run in the parent directory, and must create the
+repository's directory. So use C<git clone>, C<svn checkout>, C<bzr branch>
+or C<bzr checkout> (for a bound branch), etc.
+
+Note that these shell commands are run in a C<set -e> shell
+environment, where any additional parameters you pass are available in
+C<$@>. All commands other than "checkout" are run inside the repository,
+though not necessarily at the top of it.
+
+The C<MR_REPO> environment variable is set to the path to the top of the
+repository. (For the "register" action, "MR_REPO" is instead set to the
+basename of the directory that should be created when checking the
+repository out.)
+
+The C<MR_CONFIG> environment variable is set to the .mrconfig file
+that defines the repo being acted on, or, if the repo is not yet in a config
+file, the F<.mrconfig> file that should be modified to register the repo.
+
+The C<MR_ACTION> environment variable is set to the command being run
+(update, checkout, etc).
+
+A few parameters have special meanings:
+
+=over 4
+
+=item skip
+
+If the "skip" parameter is set and its command returns true, then B<mr>
+will skip acting on that repository. The command is passed the action
+name in C<$1>.
+
+Here are two examples. The first skips the repo unless
+mr is run by joey. The second uses the hours_since function
+(included in mr's built-in library) to skip updating the repo unless it's
+been at least 12 hours since the last update.
+
+ [mystuff]
+ checkout = ...
+ skip = test `whoami` != joey
+
+ [linux]
+ checkout = ...
+ skip = [ "$1" = update ] && ! hours_since "$1" 12
+
+Another way to use skip is for a lazy checkout. This makes mr skip
+operating on a repo unless it already exists. To enable the
+repo, you have to explicitly check it out (using "mr --force -d foo checkout").
+
+ [foo]
+ checkout = ...
+ skip = lazy
+
+=item order
+
+The "order" parameter can be used to override the default ordering of
+repositories. The default order value is 10. Use smaller values to make
+repositories be processed earlier, and larger values to make repositories
+be processed later.
+
+Note that if a repository is located in a subdirectory of another
+repository, ordering it to be processed earlier is not recommended.
+
+=item chain
+
+If the "chain" parameter is set and its command returns true, then B<mr>
+will try to load a F<.mrconfig> file from the root of the repository.
+
+=item include
+
+If the "include" parameter is set, its command is ran, and should output
+additional mrconfig file content. The content is included as if it were
+part of the including file.
+
+Unlike all other parameters, this parameter does not need to be placed
+within a section.
+
+B<mr> ships several libraries that can be included to add support for
+additional version control type things (unison, git-svn, git-fake-bare,
+git-subtree). To include them all, you could use:
+
+ include = cat /usr/share/mr/*
+
+See the individual files for details.
+
+=item deleted
+
+If the "deleted" parameter is set and its command returns true, then
+B<mr> will treat the repository as deleted. It won't ever actually delete
+the repository, but it will warn if it sees the repository's directory.
+This is useful when one mrconfig file is shared among multiple machines,
+to keep track of and remember to delete old repositories.
+
+=item lib
+
+The "lib" parameter can specify some shell code that will be run
+before each command, this can be a useful way to define shell
+functions for other commands to use.
+
+Unlike most other parameters, this can be specified multiple times, in
+which case the chunks of shell code are accumulatively concatenated
+together.
+
+=item fixups
+
+If the "fixups" parameter is set, its command is run whenever a repository
+is checked out, or updated. This provides an easy way to do things
+like permissions fixups, or other tweaks to the repository content,
+whenever the repository is changed.
+
+=item VCS_action
+
+When looking for a command to run for a given action, mr first looks for
+a parameter with the same name as the action. If that is not found, it
+looks for a parameter named "VCS_action" (substituting in the name of the
+version control system and the action).
+
+Internally, mr has settings for "git_update", "svn_update", etc. To change
+the action that is performed for a given version control system, you can
+override these VCS specific actions. To add a new version control system,
+you can just add VCS specific actions for it.
+
+=item pre_ and post_
+
+If a "pre_action" parameter is set, its command is run before mr performs the
+specified action. Similarly, "post_action" parameters are run after mr
+successfully performs the specified action. For example, "pre_commit" is
+run before committing; "post_update" is run after updating.
+
+=item _append
+
+Any parameter can be suffixed with C<_append>, to add an additional value
+to the existing value of the parameter. In this way, actions
+can be constructed accumulatively.
+
+=item VCS_test
+
+The name of the version control system is itself determined by
+running each defined "VCS_test" action, until one succeeds.
+
+=back
+
+=head1 UNTRUSTED MRCONFIG FILES
+
+Since mrconfig files can contain arbitrary shell commands, they can do
+anything. This flexibility is good, but it also allows a malicious mrconfig
+file to delete your whole home directory. Such a file might be contained
+inside a repository that your main F<~/.mrconfig> checks out. To
+avoid worries about evil commands in a mrconfig file, mr defaults to
+reading all mrconfig files other than the main F<~/.mrconfig> in untrusted
+mode. In untrusted mode, mrconfig files are limited to running only known
+safe commands (like "git clone") in a carefully checked manner.
+
+To configure mr to trust other mrconfig files, list them in F<~/.mrtrust>.
+One mrconfig file should be listed per line. Either the full pathname
+should be listed, or the pathname can start with F<~/> to specify a file
+relative to your home directory.
+
+=head1 OFFLINE LOG FILE
+
+The F<~/.mrlog> file contains commands that mr has remembered to run later,
+due to being offline. You can delete or edit this file to remove commands,
+or even to add other commands for 'mr online' to run. If the file is
+present, mr assumes it is in offline mode.
+
+=head1 EXTENSIONS
+
+mr can be extended to support things such as unison and git-svn. Some
+files providing such extensions are available in F</usr/share/mr/>. See
+the documentation in the files for details about using them.
+
+=head1 EXIT STATUS
+
+mr returns nonzero if a command failed in any of the repositories.
+
+=head1 AUTHOR
+
+Copyright 2007-2011 Joey Hess <joey@kitenet.net>
+
+Licensed under the GNU GPL version 2 or higher.
+
+http://kitenet.net/~joey/code/mr/
+
+=cut
+
+use warnings;
+use strict;
+use Getopt::Long;
+use Cwd qw(getcwd abs_path);
+
+# things that can happen when mr runs a command
+use constant {
+ OK => 0,
+ FAILED => 1,
+ SKIPPED => 2,
+ ABORT => 3,
+};
+
+# configurables
+my $config_overridden=0;
+my $verbose=0;
+my $quiet=0;
+my $stats=0;
+my $force=0;
+my $insecure=0;
+my $interactive=0;
+my $max_depth;
+my $no_chdir=0;
+my $jobs=1;
+my $trust_all=0;
+my $directory=getcwd();
+
+my $HOME_MR_CONFIG = "$ENV{HOME}/.mrconfig";
+$ENV{MR_CONFIG}=find_mrconfig();
+
+# globals :-(
+my %config;
+my %configfiles;
+my %knownactions;
+my %alias;
+my (@ok, @failed, @skipped);
+
+main();
+
+sub shellquote {
+ my $i=shift;
+ $i=~s/'/'"'"'/g;
+ return "'$i'";
+}
+
+# Runs a shell command using a supplied function.
+# The lib will be included in the shell command line, and any params
+# will be available in the shell as $1, $2, etc.
+my $lastlib;
+sub runsh {
+ my ($action, $topdir, $subdir, $command, $params, $runner) = @_;
+
+ # optimisation: avoid running the shell for true and false
+ if ($command =~ /^\s*true\s*$/) {
+ $?=0;
+ return 0;
+ }
+ elsif ($command =~ /^\s*false\s*$/) {
+ $?=0;
+ return 1;
+ }
+
+ my $quotedparams=join(" ", (map { shellquote($_) } @$params));
+ my $lib=exists $config{$topdir}{$subdir}{lib} ?
+ $config{$topdir}{$subdir}{lib}."\n" : "";
+ if ($verbose && (! defined $lastlib || $lastlib ne $lib)) {
+ print "mr library now: >>$lib<<\n";
+ $lastlib=$lib;
+ }
+ my $shellcode="set -e;".$lib.
+ "my_sh(){ $command\n }; my_sh $quotedparams";
+ print "mr $action: running $action >>$command<<\n" if $verbose;
+ $runner->($shellcode);
+}
+
+my %perl_cache;
+sub perl {
+ my $id=shift;
+ my $s=shift;
+ if ($s =~ m/^perl:\s+(.*)/s) {
+ return $perl_cache{$1} if exists $perl_cache{$1};
+ my $sub=eval "sub {$1}";
+ if (! defined $sub) {
+ print STDERR "mr: bad perl code in $id: $@\n";
+ }
+ return $perl_cache{$1} = $sub;
+ }
+ return undef;
+}
+
+my %vcs;
+sub vcs_test {
+ my ($action, $dir, $topdir, $subdir) = @_;
+
+ if (exists $vcs{$dir}) {
+ return $vcs{$dir};
+ }
+
+ my $test="";
+ my %perltest;
+ foreach my $vcs_test (
+ sort {
+ length $a <=> length $b
+ ||
+ $a cmp $b
+ } grep { /_test$/ } keys %{$config{$topdir}{$subdir}}) {
+ my ($vcs)=$vcs_test =~ /(.*)_test/;
+ my $p=perl($vcs_test, $config{$topdir}{$subdir}{$vcs_test});
+ if (defined $p) {
+ $perltest{$vcs}=$p;
+ }
+ else {
+ $test="my_$vcs_test() {\n$config{$topdir}{$subdir}{$vcs_test}\n}\n".$test;
+ $test.="if my_$vcs_test; then echo $vcs; fi\n";
+ }
+ }
+
+ my @vcs;
+ foreach my $vcs (keys %perltest) {
+ if ($perltest{$vcs}->()) {
+ push @vcs, $vcs;
+ }
+ }
+
+ push @vcs, split(/\n/,
+ runsh("vcs test", $topdir, $subdir, $test, [], sub {
+ my $sh=shift;
+ my $ret=`$sh`;
+ return $ret;
+ })) if length $test;
+ if (@vcs > 1) {
+ print STDERR "mr $action: found multiple possible repository types (@vcs) for ".fulldir($topdir, $subdir)."\n";
+ return undef;
+ }
+ if (! @vcs) {
+ return $vcs{$dir}=undef;
+ }
+ else {
+ return $vcs{$dir}=$vcs[0];
+ }
+}
+
+sub findcommand {
+ my ($action, $dir, $topdir, $subdir, $is_checkout) = @_;
+
+ if (exists $config{$topdir}{$subdir}{$action}) {
+ return $config{$topdir}{$subdir}{$action};
+ }
+
+ if ($is_checkout) {
+ return undef;
+ }
+
+ my $vcs=vcs_test(@_);
+
+ if (defined $vcs &&
+ exists $config{$topdir}{$subdir}{$vcs."_".$action}) {
+ return $config{$topdir}{$subdir}{$vcs."_".$action};
+ }
+ else {
+ return undef;
+ }
+}
+
+sub fulldir {
+ my ($topdir, $subdir) = @_;
+ return $subdir =~ /^\// ? $subdir : $topdir.$subdir;
+}
+
+sub action {
+ my ($action, $dir, $topdir, $subdir, $force_checkout) = @_;
+ my $fulldir=fulldir($topdir, $subdir);
+ my $checkout_dir;
+
+ $ENV{MR_CONFIG}=$configfiles{$topdir};
+ my $is_checkout=($action eq 'checkout');
+ my $is_update=($action =~ /update/);
+
+ ($ENV{MR_REPO}=$dir) =~ s!/$!!;
+ $ENV{MR_ACTION}=$action;
+
+ foreach my $testname ("skip", "deleted") {
+ next if $force && $testname eq "skip";
+
+ my $testcommand=findcommand($testname, $dir, $topdir, $subdir, $is_checkout);
+
+ if (defined $testcommand) {
+ my $ret=runsh "$testname test", $topdir, $subdir,
+ $testcommand, [$action],
+ sub { system(shift()) };
+ if ($ret != 0) {
+ if (($? & 127) == 2) {
+ print STDERR "mr $action: interrupted\n";
+ return ABORT;
+ }
+ elsif ($? & 127) {
+ print STDERR "mr $action: $testname test received signal ".($? & 127)."\n";
+ return ABORT;
+ }
+ }
+ if ($ret >> 8 == 0) {
+ if ($testname eq "deleted") {
+ if (-d $dir) {
+ print STDERR "mr error: $dir should be deleted yet still exists\n";
+ return FAILED;
+ }
+ }
+ print "mr $action: skip $dir skipped\n" if $verbose;
+ return SKIPPED;
+ }
+ }
+ }
+
+ if ($is_checkout) {
+ $checkout_dir=$dir;
+ if (! $force_checkout) {
+ if (-d $dir) {
+ print "mr $action: $dir already exists, skipping checkout\n" if $verbose;
+ return SKIPPED;
+ }
+
+ $dir=~s/^(.*)\/[^\/]+\/?$/$1/;
+ }
+ }
+ elsif ($is_update) {
+ if (! -d $dir) {
+ return action("checkout", $dir, $topdir, $subdir);
+ }
+ }
+
+ my $command=findcommand($action, $dir, $topdir, $subdir, $is_checkout);
+
+ if ($is_checkout && ! -d $dir) {
+ print "mr $action: creating parent directory $dir\n" if $verbose;
+ system("mkdir", "-p", $dir);
+ }
+
+ if (! $no_chdir && ! chdir($dir)) {
+ print STDERR "mr $action: failed to chdir to $dir: $!\n";
+ return FAILED;
+ }
+ elsif (! defined $command) {
+ my $vcs=vcs_test(@_);
+ if (! defined $vcs) {
+ print STDERR "mr $action: unknown repository type and no defined $action command for $fulldir\n";
+ return FAILED;
+ }
+ else {
+ print STDERR "mr $action: no defined action for $vcs repository $fulldir, skipping\n";
+ return SKIPPED;
+ }
+ }
+ else {
+ my $actionmsg;
+ if (! $no_chdir) {
+ $actionmsg="mr $action: $fulldir";
+ }
+ else {
+ my $s=$directory;
+ $s=~s/^\Q$fulldir\E\/?//;
+ $actionmsg="mr $action: $fulldir (in subdir $s)";
+ }
+ print "$actionmsg\n" unless $quiet;
+
+ my $hookret=hook("pre_$action", $topdir, $subdir);
+ return $hookret if $hookret != OK;
+
+ my $ret=runsh $action, $topdir, $subdir,
+ $command, \@ARGV, sub {
+ my $sh=shift;
+ if ($quiet) {
+ my $output = qx/$sh 2>&1/;
+ my $ret = $?;
+ if ($ret != 0) {
+ print "$actionmsg\n";
+ print STDERR $output;
+ }
+ return $ret;
+ }
+ else {
+ system($sh);
+ }
+ };
+ if ($ret != 0) {
+ if (($? & 127) == 2) {
+ print STDERR "mr $action: interrupted\n";
+ return ABORT;
+ }
+ elsif ($? & 127) {
+ print STDERR "mr $action: received signal ".($? & 127)."\n";
+ return ABORT;
+ }
+ print STDERR "mr $action: failed ($ret)\n" if $verbose;
+ if ($ret >> 8 != 0) {
+ print STDERR "mr $action: command failed\n";
+ if (-e "$ENV{HOME}/.mrlog" && $action ne 'remember') {
+ # recreate original command line to
+ # remember, and avoid recursing
+ my @orig=@ARGV;
+ @ARGV=('-n', $action, @orig);
+ action("remember", $dir, $topdir, $subdir);
+ @ARGV=@orig;
+ }
+ }
+ elsif ($ret != 0) {
+ print STDERR "mr $action: command died ($ret)\n";
+ }
+ return FAILED;
+ }
+ else {
+ if ($is_checkout && ! -d $dir) {
+ print STDERR "mr $action: $dir missing after checkout\n";;
+ return FAILED;
+ }
+
+ my $ret=hook("post_$action", $topdir, $subdir);
+ return $ret if $ret != OK;
+
+ if ($is_checkout || $is_update) {
+ if ($is_checkout && ! $no_chdir) {
+ if (! chdir($checkout_dir)) {
+ print STDERR "mr $action: failed to chdir to $checkout_dir: $!\n";
+ return FAILED;
+ }
+ }
+ my $ret=hook("fixups", $topdir, $subdir);
+ return $ret if $ret != OK;
+ }
+
+ return OK;
+ }
+ }
+}
+
+sub hook {
+ my ($hook, $topdir, $subdir) = @_;
+
+ my $command=$config{$topdir}{$subdir}{$hook};
+ return OK unless defined $command;
+ my $ret=runsh $hook, $topdir, $subdir, $command, [], sub {
+ my $sh=shift;
+ if ($quiet) {
+ my $output = qx/$sh 2>&1/;
+ my $ret = $?;
+ if ($ret != 0) {
+ print STDERR $output;
+ }
+ return $ret;
+ }
+ else {
+ system($sh);
+ }
+ };
+ if ($ret != 0) {
+ if (($? & 127) == 2) {
+ print STDERR "mr $hook: interrupted\n";
+ return ABORT;
+ }
+ elsif ($? & 127) {
+ print STDERR "mr $hook: received signal ".($? & 127)."\n";
+ return ABORT;
+ }
+ else {
+ return FAILED;
+ }
+ }
+
+ return OK;
+}
+
+# run actions on multiple repos, in parallel
+sub mrs {
+ my $action=shift;
+ my @repos=@_;
+
+ $| = 1;
+ my @active;
+ my @fhs;
+ my @out;
+ my $running=0;
+ while (@fhs or @repos) {
+ while ((!$jobs || $running < $jobs) && @repos) {
+ $running++;
+ my $repo = shift @repos;
+ pipe(my $outfh, CHILD_STDOUT);
+ pipe(my $errfh, CHILD_STDERR);
+ my $pid;
+ unless ($pid = fork) {
+ die "mr $action: cannot fork: $!" unless defined $pid;
+ open(STDOUT, ">&CHILD_STDOUT") || die "mr $action cannot reopen stdout: $!";
+ open(STDERR, ">&CHILD_STDERR") || die "mr $action cannot reopen stderr: $!";
+ close CHILD_STDOUT;
+ close CHILD_STDERR;
+ close $outfh;
+ close $errfh;
+ exit action($action, @$repo);
+ }
+ close CHILD_STDOUT;
+ close CHILD_STDERR;
+ push @active, [$pid, $repo];
+ push @fhs, [$outfh, $errfh];
+ push @out, ['', ''];
+ }
+ my ($rin, $rout) = ('','');
+ my $nfound;
+ foreach my $fh (@fhs) {
+ next unless defined $fh;
+ vec($rin, fileno($fh->[0]), 1) = 1 if defined $fh->[0];
+ vec($rin, fileno($fh->[1]), 1) = 1 if defined $fh->[1];
+ }
+ $nfound = select($rout=$rin, undef, undef, 1);
+ foreach my $channel (0, 1) {
+ foreach my $i (0..$#fhs) {
+ next unless defined $fhs[$i];
+ my $fh = $fhs[$i][$channel];
+ next unless defined $fh;
+ if (vec($rout, fileno($fh), 1) == 1) {
+ my $r = '';
+ if (sysread($fh, $r, 1024) == 0) {
+ close($fh);
+ $fhs[$i][$channel] = undef;
+ if (! defined $fhs[$i][0] &&
+ ! defined $fhs[$i][1]) {
+ waitpid($active[$i][0], 0);
+ print STDOUT $out[$i][0];
+ print STDERR $out[$i][1];
+ record($active[$i][1], $? >> 8);
+ splice(@fhs, $i, 1);
+ splice(@active, $i, 1);
+ splice(@out, $i, 1);
+ $running--;
+ }
+ }
+ $out[$i][$channel] .= $r;
+ }
+ }
+ }
+ }
+}
+
+sub record {
+ my $dir=shift()->[0];
+ my $ret=shift;
+
+ if ($ret == OK) {
+ push @ok, $dir;
+ print "\n" unless $quiet;
+ }
+ elsif ($ret == FAILED) {
+ if ($interactive) {
+ chdir($dir) unless $no_chdir;
+ print STDERR "mr: Starting interactive shell. Exit shell to continue.\n";
+ system((getpwuid($<))[8], "-i");
+ }
+ push @failed, $dir;
+ print "\n" unless $quiet;
+ }
+ elsif ($ret == SKIPPED) {
+ push @skipped, $dir;
+ }
+ elsif ($ret == ABORT) {
+ exit 1;
+ }
+ else {
+ die "unknown exit status $ret";
+ }
+}
+
+sub showstats {
+ my $action=shift;
+ if (! @ok && ! @failed && ! @skipped) {
+ die "mr $action: no repositories found to work on\n";
+ }
+ print "mr $action: finished (".join("; ",
+ showstat($#ok+1, "ok", "ok"),
+ showstat($#failed+1, "failed", "failed"),
+ showstat($#skipped+1, "skipped", "skipped"),
+ ).")\n" unless $quiet;
+ if ($stats) {
+ if (@skipped) {
+ print "mr $action: (skipped: ".join(" ", @skipped).")\n" unless $quiet;
+ }
+ if (@failed) {
+ print STDERR "mr $action: (failed: ".join(" ", @failed).")\n";
+ }
+ }
+}
+
+sub showstat {
+ my $count=shift;
+ my $singular=shift;
+ my $plural=shift;
+ if ($count) {
+ return "$count ".($count > 1 ? $plural : $singular);
+ }
+ return;
+}
+
+# an ordered list of repos
+sub repolist {
+ my @list;
+ foreach my $topdir (sort keys %config) {
+ foreach my $subdir (sort keys %{$config{$topdir}}) {
+ push @list, {
+ topdir => $topdir,
+ subdir => $subdir,
+ order => $config{$topdir}{$subdir}{order},
+ };
+ }
+ }
+ return sort {
+ $a->{order} <=> $b->{order}
+ ||
+ $a->{topdir} cmp $b->{topdir}
+ ||
+ $a->{subdir} cmp $b->{subdir}
+ } @list;
+}
+
+sub repodir {
+ my $repo=shift;
+ my $topdir=$repo->{topdir};
+ my $subdir=$repo->{subdir};
+ my $ret=($subdir =~/^\//) ? $subdir : $topdir.$subdir;
+ $ret=~s/\/\.$//;
+ return $ret;
+}
+
+# Figure out which repos to act on. Returns a list of array refs
+# in the format:
+#
+# [ "$full_repo_path/", "$mr_config_path/", $section_header ]
+sub selectrepos {
+ my @repos;
+ foreach my $repo (repolist()) {
+ my $topdir=$repo->{topdir};
+ my $subdir=$repo->{subdir};
+
+ next if $subdir eq 'DEFAULT';
+ my $dir=repodir($repo);
+ my $d=$directory;
+ $dir.="/" unless $dir=~/\/$/;
+ $d.="/" unless $d=~/\/$/;
+ next if $dir ne $d && $dir !~ /^\Q$d\E/;
+ if (defined $max_depth) {
+ my @a=split('/', $dir);
+ my @b=split('/', $d);
+ do { } while (@a && @b && shift(@a) eq shift(@b));
+ next if @a > $max_depth || @b > $max_depth;
+ }
+ push @repos, [$dir, $topdir, $subdir];
+ }
+ if (! @repos) {
+ # fallback to find a leaf repo
+ foreach my $repo (reverse repolist()) {
+ my $topdir=$repo->{topdir};
+ my $subdir=$repo->{subdir};
+
+ next if $subdir eq 'DEFAULT';
+ my $dir=repodir($repo);
+ my $d=$directory;
+ $dir.="/" unless $dir=~/\/$/;
+ $d.="/" unless $d=~/\/$/;
+ if ($d=~/^\Q$dir\E/) {
+ push @repos, [$dir, $topdir, $subdir];
+ last;
+ }
+ }
+ $no_chdir=1;
+ }
+ return @repos;
+}
+
+sub expandenv {
+ my $val=shift;
+
+
+ if ($val=~/\$/) {
+ $val=`echo "$val"`;
+ chomp $val;
+ }
+
+ return $val;
+}
+
+my %trusted;
+sub is_trusted_config {
+ my $config=shift; # must be abs_pathed already
+
+ # We always trust ~/.mrconfig.
+ return 1 if $config eq abs_path($HOME_MR_CONFIG);
+
+ return 1 if $trust_all;
+
+ my $trustfile=$ENV{HOME}."/.mrtrust";
+
+ if (! %trusted) {
+ $trusted{$HOME_MR_CONFIG}=1;
+ if (open (TRUST, "<", $trustfile)) {
+ while (<TRUST>) {
+ chomp;
+ s/^~\//$ENV{HOME}\//;
+ $trusted{abs_path($_)}=1;
+ }
+ close TRUST;
+ }
+ }
+
+ return $trusted{$config};
+}
+
+
+sub is_trusted_repo {
+ my $repo=shift;
+
+ # Tightly limit what is allowed in a repo name.
+ # No ../, no absolute paths, and no unusual filenames
+ # that might try to escape to the shell.
+ return $repo =~ /^[-_.+\/A-Za-z0-9]+$/ &&
+ $repo !~ /\.\./ && $repo !~ /^\//;
+}
+
+sub is_trusted_checkout {
+ my $command=shift;
+
+ # To determine if the command is safe, compare it with the
+ # *_trusted_checkout config settings. Those settings are
+ # templates for allowed commands, so make sure that each word
+ # of the command matches the corresponding word of the template.
+
+ my @words;
+ foreach my $word (split(' ', $command)) {
+ # strip quoting
+ if ($word=~/^'(.*)'$/) {
+ $word=$1;
+ }
+ elsif ($word=~/^"(.*)"$/) {
+ $word=$1;
+ }
+
+ push @words, $word;
+ }
+
+ foreach my $key (grep { /_trusted_checkout$/ }
+ keys %{$config{''}{DEFAULT}}) {
+ my @twords=split(' ', $config{''}{DEFAULT}{$key});
+ next if @words > @twords;
+
+ my $match=1;
+ my $url;
+ for (my $c=0; $c < @twords && $match; $c++) {
+ if ($twords[$c] eq '$url') {
+ # Match all the typical characters found in
+ # urls, plus @ which svn can use. Note
+ # that the "url" might also be a local
+ # directory.
+ $match=(
+ defined $words[$c] &&
+ $words[$c] =~ /^[-_.+:@\/A-Za-z0-9]+$/
+ );
+ $url=$words[$c];
+ }
+ elsif ($twords[$c] eq '$repo') {
+ # If a repo is not specified, assume it
+ # will be the last path component of the
+ # url, or something derived from it, and
+ # check that.
+ if (! defined $words[$c] && defined $url) {
+ ($words[$c])=$url=~/\/([^\/]+)\/?$/;
+ }
+
+ $match=(
+ defined $words[$c] &&
+ is_trusted_repo($words[$c])
+ );
+ }
+ elsif (defined $words[$c] && $words[$c]=~/^($twords[$c])$/) {
+ $match=1;
+ }
+ else {
+ $match=0;
+ }
+ }
+ return 1 if $match;
+ }
+
+ return 0;
+}
+
+my %loaded;
+sub loadconfig {
+ my $f=shift;
+ my $dir=shift;
+ my $bootstrap_url=shift;
+
+ my @toload;
+
+ my $in;
+ my $trusted;
+ if (ref $f eq 'GLOB') {
+ $dir="";
+ $in=$f;
+ $trusted=1;
+ }
+ else {
+ my $absf=abs_path($f);
+ if ($loaded{$absf}) {
+ return;
+ }
+ $loaded{$absf}=1;
+
+ $trusted=is_trusted_config($absf);
+
+ if (! defined $dir) {
+ ($dir)=$f=~/^(.*\/)[^\/]+$/;
+ if (! defined $dir) {
+ $dir=".";
+ }
+ }
+
+ $dir=abs_path($dir)."/";
+
+ if (! exists $configfiles{$dir}) {
+ $configfiles{$dir}=$f;
+ }
+
+ # copy in defaults from first parent
+ my $parent=$dir;
+ while ($parent=~s/^(.*\/)[^\/]+\/?$/$1/) {
+ if ($parent eq '/') {
+ $parent="";
+ }
+ if (exists $config{$parent} &&
+ exists $config{$parent}{DEFAULT}) {
+ $config{$dir}{DEFAULT}={ %{$config{$parent}{DEFAULT}} };
+ last;
+ }
+ }
+
+ if (! -e $f) {
+ return;
+ }
+
+ print "mr: loading config $f\n" if $verbose;
+ open($in, "<", $f) || die "mr: open $f: $!\n";
+ }
+ my @lines=<$in>;
+ close $in unless ref $f eq 'GLOB';
+
+ my $section;
+
+ # Keep track of the current line in the config file;
+ # when a file is included track the current line from the include.
+ my $lineno=0;
+ my $included=undef;
+
+ my $line;
+ my $nextline = sub {
+ if ($included) {
+ $included--;
+ }
+ else {
+ $included=undef;
+ $lineno++;
+ }
+ $line=shift @lines;
+ chomp $line;
+ return $line;
+ };
+ my $lineerror = sub {
+ my $msg=shift;
+ if (defined $included) {
+ die "mr: $msg at $f line $lineno, included line: $line\n";
+ }
+ else {
+ die "mr: $msg at $f line $lineno\n";
+ }
+ };
+ my $trusterror = sub {
+ my $msg=shift;
+
+ if (defined $bootstrap_url) {
+ die "mr: $msg in untrusted $bootstrap_url line $lineno\n".
+ "(To trust this url, --trust-all can be used; but please use caution;\n".
+ "this can allow arbitrary code execution!)\n";
+ }
+ else {
+ die "mr: $msg in untrusted $f line $lineno\n".
+ "(To trust this file, list it in ~/.mrtrust.)\n";
+ }
+ };
+
+ while (@lines) {
+ $_=$nextline->();
+
+ if (! $trusted && /[[:cntrl:]]/) {
+ $trusterror->("illegal control character");
+ }
+
+ next if /^\s*\#/ || /^\s*$/;
+ if (/^\[([^\]]*)\]\s*$/) {
+ $section=$1;
+
+ if (! $trusted) {
+ if (! is_trusted_repo($section) ||
+ $section eq 'ALIAS' ||
+ $section eq 'DEFAULT') {
+ $trusterror->("illegal section \"[$section]\"");
+ }
+ }
+ $section=expandenv($section) if $trusted;
+ if ($section ne 'ALIAS' &&
+ ! exists $config{$dir}{$section} &&
+ exists $config{$dir}{DEFAULT}) {
+ # copy in defaults
+ $config{$dir}{$section}={ %{$config{$dir}{DEFAULT}} };
+ }
+ }
+ elsif (/^(\w+)\s*=\s*(.*)/) {
+ my $parameter=$1;
+ my $value=$2;
+
+ # continued value
+ while (@lines && $lines[0]=~/^\s(.+)/) {
+ $value.="\n$1";
+ chomp $value;
+ $nextline->();
+ }
+
+ if (! $trusted) {
+ # Untrusted files can only contain a few
+ # settings in specific known-safe formats.
+ if ($parameter eq 'checkout') {
+ if (! is_trusted_checkout($value)) {
+ $trusterror->("illegal checkout command \"$value\"");
+ }
+ }
+ elsif ($parameter eq 'order') {
+ # not interpreted as a command, so
+ # safe.
+ }
+ elsif ($value eq 'true' || $value eq 'false') {
+ # skip=true , deleted=true etc are
+ # safe.
+ }
+ else {
+ $trusterror->("illegal setting \"$parameter=$value\"");
+ }
+ }
+
+ if ($parameter eq "include") {
+ print "mr: including output of \"$value\"\n" if $verbose;
+ my @inc=`$value`;
+ if ($?) {
+ print STDERR "mr: include command exited nonzero ($?)\n";
+ }
+ $included += @inc;
+ unshift @lines, @inc;
+ next;
+ }
+
+ if (! defined $section) {
+ $lineerror->("parameter ($parameter) not in section");
+ }
+ if ($section eq 'ALIAS') {
+ $alias{$parameter}=$value;
+ }
+ elsif ($parameter eq 'lib' or $parameter =~ s/_append$//) {
+ $config{$dir}{$section}{$parameter}.="\n".$value."\n";
+ }
+ else {
+ $config{$dir}{$section}{$parameter}=$value;
+ if ($parameter =~ /.*_(.*)/) {
+ $knownactions{$1}=1;
+ }
+ else {
+ $knownactions{$parameter}=1;
+ }
+ if ($parameter eq 'chain' &&
+ length $dir && $section ne "DEFAULT") {
+ my $chaindir="$section";
+ if ($chaindir !~ m!^/!) {
+ $chaindir=$dir.$chaindir;
+ }
+ if (-e "$chaindir/.mrconfig") {
+ my $ret=system($value);
+ if ($ret != 0) {
+ if (($? & 127) == 2) {
+ print STDERR "mr: chain test interrupted\n";
+ exit 2;
+ }
+ elsif ($? & 127) {
+ print STDERR "mr: chain test received signal ".($? & 127)."\n";
+ }
+ }
+ else {
+ push @toload, ["$chaindir/.mrconfig", $chaindir];
+ }
+ }
+ }
+ }
+ }
+ else {
+ $lineerror->("parse error");
+ }
+ }
+
+ foreach my $c (@toload) {
+ loadconfig(@$c);
+ }
+}
+
+sub startingconfig {
+ %alias=%config=%configfiles=%knownactions=%loaded=();
+ my $datapos=tell(DATA);
+ loadconfig(\*DATA);
+ seek(DATA,$datapos,0); # rewind
+}
+
+sub modifyconfig {
+ my $f=shift;
+ # the section to modify or add
+ my $targetsection=shift;
+ # fields to change in the section
+ # To remove a field, set its value to "".
+ my %changefields=@_;
+
+ my @lines;
+ my @out;
+
+ if (-e $f) {
+ open(my $in, "<", $f) || die "mr: open $f: $!\n";
+ @lines=<$in>;
+ close $in;
+ }
+
+ my $formatfield=sub {
+ my $field=shift;
+ my @value=split(/\n/, shift);
+
+ return "$field = ".shift(@value)."\n".
+ join("", map { "\t$_\n" } @value);
+ };
+ my $addfields=sub {
+ my @blanks;
+ while ($out[$#out] =~ /^\s*$/) {
+ unshift @blanks, pop @out;
+ }
+ foreach my $field (sort keys %changefields) {
+ if (length $changefields{$field}) {
+ push @out, "$field = $changefields{$field}\n";
+ delete $changefields{$field};
+ }
+ }
+ push @out, @blanks;
+ };
+
+ my $section;
+ while (@lines) {
+ $_=shift(@lines);
+
+ if (/^\s*\#/ || /^\s*$/) {
+ push @out, $_;
+ }
+ elsif (/^\[([^\]]*)\]\s*$/) {
+ if (defined $section &&
+ $section eq $targetsection) {
+ $addfields->();
+ }
+
+ $section=expandenv($1);
+
+ push @out, $_;
+ }
+ elsif (/^(\w+)\s*=\s(.*)/) {
+ my $parameter=$1;
+ my $value=$2;
+
+ # continued value
+ while (@lines && $lines[0]=~/^\s(.+)/) {
+ shift(@lines);
+ $value.="\n$1";
+ chomp $value;
+ }
+
+ if ($section eq $targetsection) {
+ if (exists $changefields{$parameter}) {
+ if (length $changefields{$parameter}) {
+ $value=$changefields{$parameter};
+ }
+ delete $changefields{$parameter};
+ }
+ }
+
+ push @out, $formatfield->($parameter, $value);
+ }
+ }
+
+ if (defined $section &&
+ $section eq $targetsection) {
+ $addfields->();
+ }
+ elsif (%changefields) {
+ push @out, "\n[$targetsection]\n";
+ foreach my $field (sort keys %changefields) {
+ if (length $changefields{$field}) {
+ push @out, $formatfield->($field, $changefields{$field});
+ }
+ }
+ }
+
+ open(my $out, ">", $f) || die "mr: write $f: $!\n";
+ print $out @out;
+ close $out;
+}
+
+sub dispatch {
+ my $action=shift;
+
+ # actions that do not operate on all repos
+ if ($action eq 'help') {
+ help(@ARGV);
+ }
+ elsif ($action eq 'config') {
+ config(@ARGV);
+ }
+ elsif ($action eq 'register') {
+ register(@ARGV);
+ }
+ elsif ($action eq 'bootstrap') {
+ bootstrap();
+ }
+ elsif ($action eq 'remember' ||
+ $action eq 'offline' ||
+ $action eq 'online') {
+ my @repos=selectrepos;
+ action($action, @{$repos[0]}) if @repos;
+ exit 0;
+ }
+
+ if (!$jobs || $jobs > 1) {
+ mrs($action, selectrepos());
+ }
+ else {
+ foreach my $repo (selectrepos()) {
+ record($repo, action($action, @$repo));
+ }
+ }
+}
+
+sub help {
+ exec($config{''}{DEFAULT}{help}) || die "exec: $!";
+}
+
+sub config {
+ if (@_ < 2) {
+ die "mr config: not enough parameters\n";
+ }
+ my $section=shift;
+ if ($section=~/^\//) {
+ # try to convert to a path relative to the config file
+ my ($dir)=$ENV{MR_CONFIG}=~/^(.*\/)[^\/]+$/;
+ $dir=abs_path($dir);
+ $dir.="/" unless $dir=~/\/$/;
+ if ($section=~/^\Q$dir\E(.*)/) {
+ $section=$1;
+ }
+ }
+ my %changefields;
+ foreach (@_) {
+ if (/^([^=]+)=(.*)$/) {
+ $changefields{$1}=$2;
+ }
+ else {
+ my $found=0;
+ foreach my $topdir (sort keys %config) {
+ if (exists $config{$topdir}{$section} &&
+ exists $config{$topdir}{$section}{$_}) {
+ print $config{$topdir}{$section}{$_}."\n";
+ $found=1;
+ last if $section eq 'DEFAULT';
+ }
+ }
+ if (! $found) {
+ die "mr config: $section $_ not set\n";
+ }
+ }
+ }
+ modifyconfig($ENV{MR_CONFIG}, $section, %changefields) if %changefields;
+ exit 0;
+}
+
+sub register {
+ if ($config_overridden) {
+ # Find the directory that the specified config file is
+ # located in.
+ ($directory)=abs_path($ENV{MR_CONFIG})=~/^(.*\/)[^\/]+$/;
+ }
+ else {
+ # Find the closest known mrconfig file to the current
+ # directory.
+ $directory.="/" unless $directory=~/\/$/;
+ my $foundconfig=0;
+ foreach my $topdir (reverse sort keys %config) {
+ next unless length $topdir;
+ if ($directory=~/^\Q$topdir\E/) {
+ $ENV{MR_CONFIG}=$configfiles{$topdir};
+ $directory=$topdir;
+ $foundconfig=1;
+ last;
+ }
+ }
+ if (! $foundconfig) {
+ $directory=""; # no config file, use builtin
+ }
+ }
+ if (@ARGV) {
+ my $subdir=shift @ARGV;
+ if (! chdir($subdir)) {
+ print STDERR "mr register: failed to chdir to $subdir: $!\n";
+ }
+ }
+
+ $ENV{MR_REPO}=getcwd();
+ my $command=findcommand("register", $ENV{MR_REPO}, $directory, 'DEFAULT', 0);
+ if (! defined $command) {
+ die "mr register: unknown repository type\n";
+ }
+
+ $ENV{MR_REPO}=~s/.*\/(.*)/$1/;
+ $command="set -e; ".$config{$directory}{DEFAULT}{lib}."\n".
+ "my_action(){ $command\n }; my_action ".
+ join(" ", map { s/\\/\\\\/g; s/"/\"/g; '"'.$_.'"' } @ARGV);
+ print "mr register: running >>$command<<\n" if $verbose;
+ exec($command) || die "exec: $!";
+}
+
+sub bootstrap {
+ my $url=shift @ARGV;
+ my $dir=shift @ARGV || ".";
+
+ if (! defined $url || ! length $url) {
+ die "mr: bootstrap requires url\n";
+ }
+
+ # Download the config file to a temporary location.
+ eval q{use File::Temp};
+ die $@ if $@;
+ my $tmpconfig=File::Temp->new();
+ my @downloader;
+ if ($url =~ m!^ssh://(.*)!) {
+ @downloader = ("scp", $1, $tmpconfig);
+ }
+ else {
+ @downloader = ("curl", "-A", "mr", "-L", "-s", $url, "-o", $tmpconfig);
+ push(@downloader, "-k") if $insecure;
+ }
+ my $status = system(@downloader);
+ die "mr bootstrap: invalid SSL certificate for $url (consider -k)\n"
+ if $downloader[0] eq 'curl' && $status >> 8 == 60;
+ die "mr bootstrap: download of $url failed\n" if $status != 0;
+
+ if (! -e $dir) {
+ system("mkdir", "-p", $dir);
+ }
+ chdir($dir) || die "chdir $dir: $!";
+
+ # Special case to handle checkout of the "." repo, which
+ # would normally be skipped.
+ my $topdir=abs_path(".")."/";
+ my @repo=($topdir, $topdir, ".");
+ loadconfig($tmpconfig, $topdir, $url);
+ record(\@repo, action("checkout", @repo, 1))
+ if exists $config{$topdir}{"."}{"checkout"};
+
+ if (-e ".mrconfig") {
+ print STDERR "mr bootstrap: .mrconfig file already exists, not overwriting with $url\n";
+ }
+ else {
+ eval q{use File::Copy};
+ die $@ if $@;
+ move($tmpconfig, ".mrconfig") || die "rename: $!";
+ }
+
+ # Reload the config file (in case we got a different version)
+ # and checkout everything else.
+ startingconfig();
+ loadconfig(".mrconfig");
+ dispatch("checkout");
+ @skipped=grep { abs_path($_) ne abs_path($topdir) } @skipped;
+ showstats("bootstrap");
+ exitstats();
+}
+
+# alias expansion and command stemming
+sub expandaction {
+ my $action=shift;
+ if (exists $alias{$action}) {
+ $action=$alias{$action};
+ }
+ if (! exists $knownactions{$action}) {
+ my @matches = grep { /^\Q$action\E/ }
+ keys %knownactions, keys %alias;
+ if (@matches == 1) {
+ $action=$matches[0];
+ }
+ elsif (@matches == 0) {
+ die "mr: unknown action \"$action\" (known actions: ".
+ join(", ", sort keys %knownactions).")\n";
+ }
+ else {
+ die "mr: ambiguous action \"$action\" (matches: ".
+ join(", ", @matches).")\n";
+ }
+ }
+ return $action;
+}
+
+sub find_mrconfig {
+ my $dir=getcwd();
+ while (length $dir) {
+ if (-e "$dir/.mrconfig") {
+ return "$dir/.mrconfig";
+ }
+ $dir=~s/\/[^\/]*$//;
+ }
+ return $HOME_MR_CONFIG;
+}
+
+sub getopts {
+ my @saved=@ARGV;
+ Getopt::Long::Configure("bundling", "no_permute");
+ my $result=GetOptions(
+ "d|directory=s" => sub { $directory=abs_path($_[1]) },
+ "c|config=s" => sub { $ENV{MR_CONFIG}=$_[1]; $config_overridden=1 },
+ "p|path" => sub { }, # now default, ignore
+ "f|force" => \$force,
+ "v|verbose" => \$verbose,
+ "q|quiet" => \$quiet,
+ "s|stats" => \$stats,
+ "k|insecure" => \$insecure,
+ "i|interactive" => \$interactive,
+ "n|no-recurse:i" => \$max_depth,
+ "j|jobs:i" => \$jobs,
+ "t|trust-all" => \$trust_all,
+ );
+ if (! $result || @ARGV < 1) {
+ die("Usage: mr [options] action [params ...]\n".
+ "(Use mr help for man page.)\n");
+ }
+
+ $ENV{MR_SWITCHES}="";
+ foreach my $option (@saved) {
+ last if $option eq $ARGV[0];
+ $ENV{MR_SWITCHES}.="$option ";
+ }
+}
+
+sub init {
+ $SIG{INT}=sub {
+ print STDERR "mr: interrupted\n";
+ exit 2;
+ };
+
+ # This can happen if it's run in a directory that was removed
+ # or other strangeness.
+ if (! defined $directory) {
+ die("mr: failed to determine working directory\n");
+ }
+ # Make sure MR_CONFIG is an absolute path, but don't use abs_path since
+ # the config file might be a symlink to elsewhere, and the directory it's
+ # in is significant.
+ if ($ENV{MR_CONFIG} !~ /^\//) {
+ $ENV{MR_CONFIG}=getcwd()."/".$ENV{MR_CONFIG};
+ }
+ # Try to set MR_PATH to the path to the program.
+ eval {
+ use FindBin qw($Bin $Script);
+ $ENV{MR_PATH}=$Bin."/".$Script;
+ };
+}
+
+sub exitstats {
+ if (@failed) {
+ exit 1;
+ }
+ else {
+ exit 0;
+ }
+}
+
+sub main {
+ getopts();
+ init();
+
+ startingconfig();
+ loadconfig($HOME_MR_CONFIG);
+ loadconfig($ENV{MR_CONFIG});
+ #use Data::Dumper; print Dumper(\%config);
+
+ my $action=expandaction(shift @ARGV);
+ dispatch($action);
+
+ showstats($action);
+ exitstats();
+}
+
+# Finally, some useful actions that mr knows about by default.
+# These can be overridden in ~/.mrconfig.
+__DATA__
+[ALIAS]
+co = checkout
+ci = commit
+ls = list
+
+[DEFAULT]
+order = 10
+lib =
+ error() {
+ echo "mr: $@" >&2
+ exit 1
+ }
+ warning() {
+ echo "mr (warning): $@" >&2
+ }
+ info() {
+ echo "mr: $@" >&2
+ }
+ hours_since() {
+ if [ -z "$1" ] || [ -z "$2" ]; then
+ error "mr: usage: hours_since action num"
+ fi
+ for dir in .git .svn .bzr CVS .hg _darcs _FOSSIL_; do
+ if [ -e "$MR_REPO/$dir" ]; then
+ flagfile="$MR_REPO/$dir/.mr_last$1"
+ break
+ fi
+ done
+ if [ -z "$flagfile" ]; then
+ error "cannot determine flag filename"
+ fi
+ delta=`perl -wle 'print -f shift() ? int((-M _) * 24) : 9999' "$flagfile"`
+ if [ "$delta" -lt "$2" ]; then
+ return 1
+ else
+ touch "$flagfile"
+ return 0
+ fi
+ }
+ is_bzr_checkout() {
+ LANG=C bzr info | egrep -q '^Checkout'
+ }
+ lazy() {
+ if [ -d "$MR_REPO" ]; then
+ return 1
+ else
+ return 0
+ fi
+ }
+
+svn_test = perl: -d "$ENV{MR_REPO}/.svn"
+git_test = perl: -e "$ENV{MR_REPO}/.git"
+bzr_test = perl: -d "$ENV{MR_REPO}/.bzr"
+cvs_test = perl: -d "$ENV{MR_REPO}/CVS"
+hg_test = perl: -d "$ENV{MR_REPO}/.hg"
+darcs_test = perl: -d "$ENV{MR_REPO}/_darcs"
+fossil_test = perl: -f "$ENV{MR_REPO}/_FOSSIL_"
+git_bare_test = perl:
+ -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" &&
+ -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" &&
+ `GIT_CONFIG="$ENV{MR_REPO}"/config git config --get core.bare` =~ /true/
+vcsh_test = perl:
+ -d "$ENV{MR_REPO}/refs/heads" && -d "$ENV{MR_REPO}/refs/tags" &&
+ -d "$ENV{MR_REPO}/objects" && -f "$ENV{MR_REPO}/config" &&
+ `GIT_CONFIG="$ENV{MR_REPO}"/config git config --get vcsh.vcsh` =~ /true/
+veracity_test = perl: -d "$ENV{MR_REPO}/.sgdrawer"
+
+svn_update = svn update "$@"
+git_update = git pull "$@"
+bzr_update =
+ if is_bzr_checkout; then
+ bzr update "$@"
+ else
+ bzr merge --pull "$@"
+ fi
+cvs_update = cvs -q update "$@"
+hg_update = hg pull "$@"; hg update "$@"
+darcs_update = darcs pull -a "$@"
+fossil_update = fossil pull "$@"
+vcsh_update = vcsh run "$MR_REPO" git pull "$@"
+veracity_update = vv pull "$@" && vv update "$@"
+
+git_fetch = git fetch --all --prune --tags
+git_svn_fetch = git svn fetch
+darcs_fetch = darcs fetch
+hg_fetch = hg pull
+
+svn_status = svn status "$@"
+git_status = git status -s "$@" || true; git --no-pager log --branches --not --remotes --simplify-by-decoration --decorate --oneline || true
+bzr_status = bzr status --short "$@"; bzr missing
+cvs_status = cvs -q status | grep -E '^(File:.*Status:|\?)' | grep -v 'Status: Up-to-date'
+hg_status = hg status "$@"; hg summary --quiet | grep -v 'parent: 0:'
+darcs_status = darcs whatsnew -ls "$@" || true
+fossil_status = fossil changes "$@"
+vcsh_status = vcsh run "$MR_REPO" git -c status.relativePaths=false status -s "$@" || true
+veracity_status = vv status "$@"
+
+svn_commit = svn commit "$@"
+git_commit = git commit -a "$@" && git push --all
+bzr_commit =
+ if is_bzr_checkout; then
+ bzr commit "$@"
+ else
+ bzr commit "$@" && bzr push
+ fi
+cvs_commit = cvs commit "$@"
+hg_commit = hg commit "$@" && hg push
+darcs_commit = darcs record -a "$@" && darcs push -a
+fossil_commit = fossil commit "$@"
+vcsh_commit = vcsh run "$MR_REPO" git commit -a "$@" && vcsh run "$MR_REPO" git push --all
+veracity_commit = vv commit "$@" && vv push
+
+git_record = git commit -a "$@"
+bzr_record =
+ if is_bzr_checkout; then
+ bzr commit --local "$@"
+ else
+ bzr commit "$@"
+ fi
+hg_record = hg commit "$@"
+darcs_record = darcs record -a "$@"
+fossil_record = fossil commit "$@"
+vcsh_record = vcsh run "$MR_REPO" git commit -a "$@"
+veracity_record = vv commit "$@"
+
+svn_push = :
+git_push = git push "$@"
+bzr_push = bzr push "$@"
+cvs_push = :
+hg_push = hg push "$@"
+darcs_push = darcs push -a "$@"
+fossil_push = fossil push "$@"
+vcsh_push = vcsh run "$MR_REPO" git push "$@"
+veracity_push = vv push "$@"
+
+svn_diff = svn diff "$@"
+git_diff = git diff "$@"
+bzr_diff = bzr diff "$@"
+cvs_diff = cvs -q diff "$@"
+hg_diff = hg diff "$@"
+darcs_diff = darcs diff -u "$@"
+fossil_diff = fossil diff "$@"
+vcsh_diff = vcsh run "$MR_REPO" git diff "$@"
+veracity_diff = vv diff "$@"
+
+svn_log = svn log "$@"
+git_log = git log "$@"
+bzr_log = bzr log "$@"
+cvs_log = cvs log "$@"
+hg_log = hg log "$@"
+darcs_log = darcs changes "$@"
+git_bare_log = git log "$@"
+fossil_log = fossil timeline "$@"
+vcsh_log = vcsh run "$MR_REPO" git log "$@"
+veracity_log = vv log "$@"
+
+hg_grep = hg grep "$@"
+cvs_grep = ack-grep "$@"
+svn_grep = ack-grep "$@"
+git_svn_grep = git grep "$@"
+git_grep = git grep "$@"
+bzr_grep = ack-grep "$@"
+
+run = "$@"
+
+svn_register =
+ url=`LC_ALL=C svn info . | grep -i '^URL:' | cut -d ' ' -f 2`
+ if [ -z "$url" ]; then
+ error "cannot determine svn url"
+ fi
+ echo "Registering svn url: $url in $MR_CONFIG"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="svn co '$url' '$MR_REPO'"
+git_register =
+ url="`LC_ALL=C git config --get remote.origin.url`" || true
+ if [ -z "$url" ]; then
+ error "cannot determine git url"
+ fi
+ echo "Registering git url: $url in $MR_CONFIG"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone '$url' '$MR_REPO'"
+bzr_register =
+ url="`LC_ALL=C bzr info . | egrep -i 'checkout of branch|parent branch' | awk '{print $NF}' | head -n 1`"
+ if [ -z "$url" ]; then
+ error "cannot determine bzr url"
+ fi
+ echo "Registering bzr url: $url in $MR_CONFIG"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="bzr branch '$url' '$MR_REPO'"
+cvs_register =
+ repo=`cat CVS/Repository`
+ root=`cat CVS/Root`
+ if [ -z "$root" ]; then
+ error "cannot determine cvs root"
+ fi
+ echo "Registering cvs repository $repo at root $root"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="cvs -d '$root' co -d '$MR_REPO' '$repo'"
+hg_register =
+ url=`hg showconfig paths.default`
+ echo "Registering mercurial repo url: $url in $MR_CONFIG"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="hg clone '$url' '$MR_REPO'"
+darcs_register =
+ url=`cat _darcs/prefs/defaultrepo`
+ echo "Registering darcs repository $url in $MR_CONFIG"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="darcs get '$url' '$MR_REPO'"
+git_bare_register =
+ url="`LC_ALL=C GIT_CONFIG=config git config --get remote.origin.url`" || true
+ if [ -z "$url" ]; then
+ error "cannot determine git url"
+ fi
+ echo "Registering git url: $url in $MR_CONFIG"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="git clone --bare '$url' '$MR_REPO'"
+vcsh_register =
+ url="`LC_ALL=C vcsh run "$MR_REPO" git config --get remote.origin.url`" || true
+ if [ -z "$url" ]; then
+ error "cannot determine git url"
+ fi
+ echo "Registering git url: $url in $MR_CONFIG"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="vcsh clone '$url' '$MR_REPO'"
+fossil_register =
+ url=`fossil remote-url`
+ repo=`fossil info | grep repository | sed -e 's/repository:*.//g' -e 's/ //g'`
+ echo "Registering fossil repository $url in $MR_CONFIG"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && fossil open '$repo'"
+veracity_register =
+ url=`vv config | grep sync_targets | sed -e 's/sync_targets:*.//g' -e 's/ //g'`
+ repo=`vv repo info | grep repository | sed -e 's/Current repository:*.//g' -e 's/ //g'`
+ echo "Registering veracity repository $url in $MR_CONFIG"
+ mr -c "$MR_CONFIG" config "`pwd`" checkout="mkdir -p '$MR_REPO' && cd '$MR_REPO' && vv checkout '$repo'"
+
+svn_trusted_checkout = svn co $url $repo
+svn_alt_trusted_checkout = svn checkout $url $repo
+git_trusted_checkout = git clone $url $repo
+bzr_trusted_checkout = bzr checkout|clone|branch|get $url $repo
+# cvs: too hard
+hg_trusted_checkout = hg clone $url $repo
+darcs_trusted_checkout = darcs get $url $repo
+git_bare_trusted_checkout = git clone --bare $url $repo
+vcsh_trusted_checkout = vcsh run "$MR_REPO" git clone $url $repo
+# fossil: messy to do
+veracity_trusted_checkout = vv clone $url $repo
+
+
+help =
+ case `uname -s` in
+ SunOS)
+ SHOWMANFILE="man -f"
+ ;;
+ Darwin)
+ SHOWMANFILE="man"
+ ;;
+ *)
+ SHOWMANFILE="man -l"
+ ;;
+ esac
+ if [ ! -e "$MR_PATH" ]; then
+ error "cannot find program path"
+ fi
+ tmp=$(mktemp -t mr.XXXXXXXXXX) || error "mktemp failed"
+ trap "rm -f $tmp" exit
+ pod2man -c mr "$MR_PATH" > "$tmp" || error "pod2man failed"
+ $SHOWMANFILE "$tmp" || error "man failed"
+list = true
+config =
+bootstrap =
+
+online =
+ if [ -s ~/.mrlog ]; then
+ info "running offline commands"
+ mv -f ~/.mrlog ~/.mrlog.old
+ if ! sh -e ~/.mrlog.old; then
+ error "offline command failed; left in ~/.mrlog.old"
+ fi
+ rm -f ~/.mrlog.old
+ else
+ info "no offline commands to run"
+ fi
+offline =
+ umask 077
+ touch ~/.mrlog
+ info "offline mode enabled"
+remember =
+ info "remembering command: 'mr $@'"
+ command="mr -d '$(pwd)' $MR_SWITCHES"
+ for w in "$@"; do
+ command="$command '$w'"
+ done
+ if [ ! -e ~/.mrlog ] || ! grep -q -F "$command" ~/.mrlog; then
+ echo "$command" >> ~/.mrlog
+ fi
+
+ed = echo "A horse is a horse, of course, of course.."
+T = echo "I pity the fool."
+right = echo "Not found."
+
+# vim:sw=8:sts=0:ts=8:noet
+# Local variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 8
+# End:
--- /dev/null
+# A simple example config file for the mr(1) command.
+
+[mr]
+checkout = git clone git://git.kitenet.net/mr
+
+[linux-2.6]
+checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
+# only update once every 12 hours
+skip = ([ "$1" = update ] && ! hours_since "$1" 12)
+
+[debian-cd]
+checkout = svn co svn://svn.debian.org/debian-cd/trunk debian-cd
--- /dev/null
+# An example config file for the mr(1) command.
+#
+# This is fairly close to the config file used by the author
+# although slightly cut down.
+
+[DEFAULT]
+# Include all available libs.
+include = cat /usr/share/mr/* 2>/dev/null || true
+# Teach mr to run a few git and svn specific commands.
+svn_cleanup = svn cleanup "$@"
+git_gc = git gc "$@"
+git_tags = git tag -l
+svn_tags = svn ls "$(LC_ALL=C svn info . | grep -i ^URL: | cut -d ' ' -f 2 | sed -e 's/trunk/tags/')"
+# I prefer to git-svn rebase to fetch
+git_svn_update = git svn rebase
+# Tests used below.
+# - anon checks whether this is an anonymous checkout, by testing what url
+# $HOME uses
+# - full checks whether I probably want a full checkout (quite large),
+# if not, the checkout is minimal
+# - on checks whether the given host basename is one of the listed
+# values. A value can also have a username in it, ie "joey@dodo".
+# - mylaptop only succeeds if it's on my main development laptop, which
+# gets lots of extra cruft
+lib =
+ hostname="$(hostname)"
+ whoami="$(whoami)"
+ anon() {
+ ( GIT_CONFIG=$HOME/.git/config git config remote.origin.url || cat .git/remotes/origin ) | grep -q 'git://'
+ }
+ full() {
+ test "$whoami" = joey && ! anon
+ }
+ on() {
+ for host in $@; do
+ if [ "${host%@*}" != "${host#*@}" ]; then
+ if [ "$whoami" != "${host%@*}" ]; then
+ continue
+ fi
+ host="${host#*@}"
+ fi
+ if [ "$hostname" = "$host" ]; then
+ return 0
+ fi
+ done
+ return 1
+ }
+ mylaptop() {
+ on joey@gnu
+ }
+
+# The root of my home directory.
+[.]
+order = 1
+checkout =
+ if anon; then
+ git clone git://git.kitenet.net/joey/home joey
+ else
+ git clone ssh://git.kitenet.net/srv/git/kitenet.net/joey/home joey
+ fi
+
+[Maildir]
+# Dummy target to create Maildir. Doesn't run offlineimap since I have that
+# cronned on machines where I want it.
+update = :
+skip = ! full
+checkout = mkdir Maildir Maildir/cur Maildir/new Maildir/tmp; chmod 700 Maildir
+status = :
+
+[mail]
+checkout = git clone ssh://joey@git.kitenet.net/srv/git/joey/private/mail
+# I use mairix to index my mail archive; keep its index up-to-date.
+fixups = if [ "$(which mairix)" ]; then ionice -c 3 mairix -Q; fi
+skip = ! mylaptop
+
+[tmp]
+# This is a dummy target, all it does is run fixups at the end of
+# an update.
+fixups = $HOME/bin/fixups
+checkout = mkdir -p $HOME/tmp
+status = :
+order = 25
+
+[.etc]
+order = 2
+checkout =
+ if anon; then
+ git clone git://git.kitenet.net/joey/home-etc .etc
+ else
+ git clone ssh://git.kitenet.net/srv/git/kitenet.net/joey/home-etc .etc
+ fi
+
+[.cron]
+checkout =
+ if anon; then
+ git clone git://git.kitenet.net/joey/cron .cron
+ else
+ git clone ssh://git.kitenet.net/srv/git/kitenet.net/joey/cron .cron
+ fi
+
+[packages/perl]
+order = 30
+checkout = svn co svn+ssh://joeyh@svn.debian.org/svn/pkg-perl/trunk perl
+skip = ! mylaptop || ([ "$1" = update ] && ! hours_since "$1" 12)
+
+[linux-2.6]
+order = 20
+checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
+skip = ! mylaptop || ([ "$1" = update ] && ! hours_since "$1" 12)
+push = error "as if!"
+
+[/etc]
+# I use etckeeper to keep /etc in git. But it only works if I'm root,
+# and if it's not already in etc, skip it.
+skip = ! test -d /etc/.git || ! test "$(whoami)" = root
+
+[dpkg]
+# A merge of the upstream dpkg git repo and my own personal branch.
+checkout =
+ git clone git://git.debian.org/git/dpkg/dpkg.git &&
+ cd dpkg &&
+ git remote add kite ssh://git.kitenet.net/srv/git/kitenet.net/dpkg &&
+ git fetch kite &&
+ git checkout -b sourcev3 kite/sourcev3
+update = git pull origin master && git pull kite sourcev3
+commit = git commit -a && git push kite
+push = git push kite
+skip = ! mylaptop
+
+[html/www.debian.org]
+# Still in CVS..urk!
+checkout = cvs -d ':ext:joeyh@cvs.debian.org:/cvs/webwml' co -d www.debian.org webwml
+# cvs sucks sufficiently that I prefer to run these commands by hand,
+# and only rarely
+update = echo "skipping cvs update (too slow)"
+status = echo "skipping cvs status (too ugly)"
+skip = ! mylaptop || ! full
--- /dev/null
+#!/usr/bin/perl
+
+=head1 NAME
+
+webcheckout - check out repositories referenced on a web page
+
+=head1 SYNOPSIS
+
+B<webcheckout> [options] url [destdir]
+
+=head1 DESCRIPTION
+
+B<webcheckout> downloads an url and parses it, looking for version control
+repositories referenced by the page. It checks out each repository into
+a subdirectory of the current directory, using whatever VCS program is
+appropriate for that repository (git, svn, etc).
+
+The information about the repositories is embedded in the web page using
+the rel=vcs-* microformat, which is documented at
+<http://kitenet.net/~joey/rfc/rel-vcs/>.
+
+If the optional destdir parameter is specified, VCS programs will be asked
+to check out repositories into that directory. If there are multiple
+repositories to check out, each will be checked out into a separate
+subdirectory of the destdir.
+
+=head1 OPTIONS
+
+=over 4
+
+=item -a, --auth
+
+Prefer authenticated repositories. By default, webcheckout will use
+anonymous repositories when possible. If you have an account that
+allows you to use authenticated repositories, you might want to use this
+option.
+
+=item --no-act, -n
+
+Do not actually check anything out, just print out the commands that would
+be run to check out the repositories.
+
+=item --quiet, -q
+
+Quiet mode. Do not print out the commands being run. (The VCS commands
+may still be noisy however.)
+
+=back
+
+=head1 PREREQUISITES
+
+To use this program you will need lots of VCS programs installed,
+obviously. It also depends on the perl LWP and HTML::Parser modules.
+
+If the perl URI module is installed, webcheckout can heuristically guess
+what you mean by partial URLs, such as "kitenet.net/~joey"'
+
+=head1 AUTHOR
+
+Copyright 2009 Joey Hess <joey@kitenet.net>
+
+Licensed under the GNU GPL version 2 or higher.
+
+This program is included in mr <http://kitenet.net/~joey/code/mr/>
+
+=cut
+
+use LWP::Simple;
+use HTML::Parser;
+use Getopt::Long;
+use warnings;
+use strict;
+
+# What to download.
+my $url;
+
+# Controls whether to print what is being done.
+my $quiet=0;
+
+# Controls whether to actually check anything out.
+my $noact=0;
+
+# Controls whether to perfer repos that use authentication.
+my $want_auth=0;
+
+# Controls where to check out to. If not set, the VCS is allowed to
+# decide.
+my $destdir;
+
+# how to perform checkouts
+my %handlers=(
+ git => sub { doit("git", "clone", shift, $destdir) },
+ svn => sub { doit("svn", "checkout", shift, $destdir) },
+ bzr => sub { doit("bzr", "branch", shift, $destdir) },
+);
+
+# Regexps matching urls that are used for anonymous
+# repository checkouts. The order is significant:
+# urls matching earlier in the list are preferred over
+# those matching later.
+my @anon_urls=(
+ qr/^git:\/\//i,
+ qr/^bzr:\/\//i,
+ qr/^svn:\/\//i,
+ qr/^http:\/\//i, # generally the worst transport
+);
+
+sub getopts {
+ Getopt::Long::Configure("bundling", "no_permute");
+ my $result=GetOptions(
+ "q|quiet" => \$quiet,
+ "n|noact" => \$noact,
+ "a|auth", => \$want_auth,
+ );
+ if (! $result || @ARGV < 1) {
+ die "usage: webcheckout [options] url [destdir]\n";
+ }
+
+ $url=shift @ARGV;
+ $destdir=shift @ARGV;
+
+ eval q{use URI::Heuristic};
+ if (! $@) {
+ $url=URI::Heuristic::uf_uristr($url);
+ }
+
+ if ($noact) {
+ $quiet=0;
+ }
+}
+
+sub doit {
+ my @args=grep { defined } @_;
+ print join(" ", @args)."\n" unless $quiet;
+ return 0 if $noact;
+ return system(@args);
+}
+
+# Is repo a better than repo b?
+sub better {
+ my ($a, $b)=@_;
+
+ my @anon;
+ foreach my $r (@anon_urls) {
+ if ($a->{href} =~ /$r/) {
+ push @anon, $a;
+ }
+ elsif ($b->{href} =~ /$r/) {
+ push @anon, $b;
+ }
+ }
+
+ if ($want_auth) {
+ # Whichever is authed is better.
+ return 1 if ! @anon || ! grep { $_ eq $a } @anon;
+ return 0 if ! grep { $_ eq $b } @anon;
+ # Neither is authed, so the better anon method wins.
+ return $anon[0] == $a;
+ }
+ else {
+ # Better anon method wins.
+ return @anon && $anon[0] == $a;
+ }
+}
+
+# Eliminate duplicate repositories from list.
+# Duplicate repositories have the same title, or the same href.
+sub dedup {
+ my %seenhref;
+ my %bytitle;
+ my @others;
+ foreach my $repo (@_) {
+ if (exists $repo->{title} &&
+ length $repo->{title}) {
+ if (exists $bytitle{$repo->{title}}) {
+ my $other=$bytitle{$repo->{title}};
+ next unless better($repo, $other);
+ delete $bytitle{$other->{title}}
+ }
+
+ if (! $seenhref{$repo->{href}}++) {
+ $bytitle{$repo->{title}}=$repo;
+ }
+ }
+ else {
+ push @others, $repo;
+ }
+ }
+
+ return values %bytitle, @others;
+}
+
+sub parse {
+ my $page=shift;
+
+ my @ret;
+ my $parser=HTML::Parser->new(api_version => 3);
+ my $abody=undef;
+ my $aref=undef;
+ $parser->handler(start => sub {
+ my $tagname=shift;
+ my $attr=shift;
+
+ return if ! exists $attr->{href} || ! length $attr->{href};
+ return if ! exists $attr->{rel} || $attr->{rel} !~ /^vcs-(.+)/i;
+ $attr->{type}=lc($1);
+
+ # need to collect the body of the <a> tag if there is no title
+ if ($tagname eq "a" && ! exists $attr->{title}) {
+ $abody="";
+ $aref=$attr;
+ }
+
+ push @ret, $attr;
+ }, "tagname, attr");
+ $parser->handler(text => sub {
+ if (defined $aref) {
+ $abody.=join(" ", @_);
+ }
+ }, "text");
+ $parser->handler(end => sub {
+ my $tagname=shift;
+ if ($tagname eq "a" && defined $aref) {
+ $aref->{title}=$abody;
+ $aref=undef;
+ $abody=undef;
+ }
+ }, "tagname");
+ $parser->report_tags(qw{link a});
+ $parser->parse($page);
+ $parser->eof;
+
+ return @ret;
+}
+
+getopts();
+
+my $page=get($url);
+if (! defined $page) {
+ die "failed to download $url\n";
+}
+
+my @repos=dedup(parse($page));
+if (! @repos) {
+ die "no repositories found on $url\n";
+}
+
+#use Data::Dumper;
+#print Dumper(\@repos);
+#exit;
+
+if (defined $destdir && @repos > 1) {
+ # create subdirs of $destdir for the multiple repos
+ if (! $noact) {
+ mkdir($destdir);
+ chdir($destdir) || die "failed to chdir to $destdir: $!";
+ }
+ $destdir=undef;
+}
+
+my $errors=0;
+foreach my $repo (@repos) {
+ my $handler=$handlers{$repo->{type}};
+ if ($handler) {
+ if ($handler->($repo->{href}) != 0) {
+ print STDERR "failed to checkout ".$repo->{href}."\n";
+ $errors++;
+ }
+ }
+ else {
+ print STDERR "unknown repository type ".$repo->{type}.
+ " for ".$repo->{href}."\n";
+ $errors++;
+ }
+}
+exit($errors > 0);