myrepos/0000755000000000000000000000000012206701552007442 5ustar myrepos/debian/0000755000000000000000000000000012206701621010661 5ustar myrepos/debian/NEWS0000644000000000000000000000116712165601744011376 0ustar 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 Wed, 19 Jan 2011 13:40:16 -0400 myrepos/debian/control0000644000000000000000000000305612204457013012271 0ustar Source: myrepos Section: vcs Priority: optional Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.9.0) Maintainer: Joey Hess Standards-Version: 3.9.4 Homepage: http://myrepos.branchable.com/ Vcs-Git: git://myrepos.branchable.com/ Package: myrepos Architecture: all Section: vcs Depends: ${misc:Depends} Suggests: subversion, git-core | git (>= 1:1.7), cvs, bzr, mercurial, darcs, fossil, vcsh, liburi-perl, curl, ack-grep Provides: mr Replaces: mr Recommends: libwww-perl, libhtml-parser-perl, perl Description: tool to manage all your version control repos 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. myrepos/debian/docs0000644000000000000000000000000712165610163011535 0ustar README myrepos/debian/rules0000755000000000000000000000020312165606037011745 0ustar #!/usr/bin/make -f %: dh $@ # Not intended for use by anyone except the author. announcedir: @echo ${HOME}/src/myrepos/doc/news myrepos/debian/examples0000644000000000000000000000003212165601744012426 0ustar mrconfig mrconfig.complex myrepos/debian/changelog0000644000000000000000000005602612206701621012544 0ustar myrepos (1.20130826) unstable; urgency=low * Update of manpage and Suggests field for 'mr grep'. Closes: #720128 -- Joey Hess Mon, 26 Aug 2013 12:32:39 -0400 myrepos (1.20130710) unstable; urgency=low * Avoid conflicting with mr so the dummy package can be installed and pull in this one. -- Joey Hess Wed, 10 Jul 2013 13:50:48 -0400 myrepos (1.20130705.1) unstable; urgency=low * Conflict and Replaces mr. -- Joey Hess Fri, 05 Jul 2013 16:55:30 -0400 myrepos (1.20130705) unstable; urgency=low * The package is renamed to myrepos. It Provides mr, so can still be installed by that name. The mr command is not renamed. * Add make install rule. Thanks, v4hn -- Joey Hess Fri, 05 Jul 2013 14:42:39 -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 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 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 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 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 Tue, 14 Feb 2012 18:13:29 -0400 mr (1.10) unstable; urgency=low * Fix display of trust errors. -- Joey Hess 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 Fri, 16 Dec 2011 13:29:40 -0400 mr (1.08) unstable; urgency=low * Fix vcs test code. Closes: #651976 -- Joey Hess 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 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 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 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 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 Sun, 01 May 2011 19:09:35 -0400 mr (1.02) unstable; urgency=low * Fix bug in escaping. -- Joey Hess 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 Wed, 21 Jan 2009 01:40:59 -0500 mr (0.36) unstable; urgency=low * Add webcheckout command. See * Fix display of repos configured at absolute path locations. -- Joey Hess 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 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 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 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 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 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 register. (Thanks, Daniel Bungert) -- Joey Hess 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 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 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 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 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 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 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 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 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 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 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 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 Thu, 29 Nov 2007 17:48:09 -0500 mr (0.17) unstable; urgency=low * Even bigger hammer: Set LC_ALL. Closes: #453305 -- Joey Hess 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 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 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 Sun, 11 Nov 2007 01:10:18 -0500 mr (0.13) unstable; urgency=low * -c was broken, fix. Closes: #449539 -- Joey Hess 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 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 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 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 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 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 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 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 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 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 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 Tue, 16 Oct 2007 02:39:08 -0400 mr (0.1) unstable; urgency=low * First release. -- Joey Hess Sun, 14 Oct 2007 14:14:40 -0400 myrepos/debian/copyright0000644000000000000000000000037412165601744012631 0ustar Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Files: * Copyright: (c) 2007-2011 Joey Hess License: GPL-2+ On Debian systems, the complete text of the GPL can be found in /usr/share/common-licenses/GPL. myrepos/debian/compat0000644000000000000000000000000212165601744012070 0ustar 9 myrepos/README0000644000000000000000000000266112165605634010337 0ustar myrepos, a tool to manage all your version control repos You have a lot of version control repositories. Sometimes you want to update them all at once. Or push out all your local changes. You use special command lines in some repositories to implement specific workflows. Myrepos provides a `mr` command, which is a tool to manage all your version control repositories. It supports git, svn, mercurial, bzr, darcs, cvs, fossil and veracity. Author: Joey Hess Homepage: http://myrepos.branchable.com/ The mr command 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 or the website, http://myrepos.branchable.com/ myrepos/Makefile0000644000000000000000000000134012165601744011106 0ustar 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) myrepos/mrconfig.complex0000644000000000000000000000761212165601744012653 0ustar # 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 myrepos/doc/0000755000000000000000000000000012165607773010225 5ustar myrepos/doc/news/0000755000000000000000000000000012167320302011157 5ustar myrepos/doc/news/version_1.20130710.mdwn0000644000000000000000000000027512167320302014653 0ustar myrepos 1.20130710 released with [[!toggle text="these changes"]] [[!toggleable text=""" * Avoid conflicting with mr so the dummy package can be installed and pull in this one."""]]myrepos/doc/news/version_1.20130705.1.mdwn0000644000000000000000000000017612165632172015027 0ustar myrepos 1.20130705.1 released with [[!toggle text="these changes"]] [[!toggleable text=""" * Conflict and Replaces mr."""]]myrepos/doc/news/version_1.20130705.mdwn0000644000000000000000000000041712165612002014654 0ustar myrepos 1.20130705 released with [[!toggle text="these changes"]] [[!toggleable text=""" * The package is renamed to myrepos. It Provides mr, so can still be installed by that name. The mr command is not renamed. * Add make install rule. Thanks, v4hn"""]]myrepos/doc/news/mr_renamed_to_myrepos.mdwn0000644000000000000000000000030512165601744016447 0ustar I've renamed mr because myrepos is a more descriptive name that's easier to search for. The `mr` command itself will not be renamed, this is only a renaming of the package. [[!meta author=Joey]] myrepos/doc/index.mdwn0000644000000000000000000000577512165607471012234 0ustar You have a lot of version control repositories. Sometimes you want to update them all at once. Or push out all your local changes. You use special command lines in some repositories to implement specific workflows. Myrepos provides a `mr` command, which is a tool to manage all your version control repositories. ## getting started All you need to get started is some already checked out repos. These could be using git, or bzr, mercurial or darcs, or many other version control systems. Doesn't matter, they're all supported! Inside each of your repositories, run `mr register`. That sets up a `~/.mrconfig` file listing your repositories. Now you can run `mr update` in your home directory, and it'll update every one of your repositories that you've registered with myrepos. Want to update repositories in parallel? `mr update -j5` will run 5 concurrent jobs! If you run `mr update` inside a repository, it'll only act on that repository. In general, any `mr` command runs recursively over any repository located somewhere in or under the current directory. You can also run `mr commit`, `mr push`, `mr status`, `mr diff`, and a lot of other commands. These work no matter which version control system is used for a repository. Of course, you can still use the native version control commands too. Oh, and you can abbreviate any command to any unambiguous abbreviation. `mr up`, `mr pu`, etc. Now, maybe you find that you always want to update one repository using `git pull --rebase`, instead of the default `git pull` that `mr update` runs. No problem: The `~/.mrconfig` file makes it easy to override the command run for any repository. It's like a `Makefile` for repositories. [foo] checkout = git@github.com:joeyh/foo.git update = git pull --rebase You can make up your own commands too: [bar] # This repository has an upstream, which I've forked; # set up a remote on checkout. checkout = git clone git@github.com:joeyh/bar.git cd bar git remote add upstream git@github.com:barbar/bar.git # make `mr zap` integrate from upstream zap = git pull upstream git merge upstream/master git push origin master You can even define commands globally, so `mr` can use them in all repositories. [DEFAULT] # Teach mr how to `mr gc` in git repos. git_gc = git gc "$@" This only scratches the surface of the ways you can use myrepos to automate and mange your repositories! Some more 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. * Remember actions that failed due to a laptop being offline, so they can be retried when it comes back online. * Combine several related repositories into a single logical repository, with its own top-level `.mrconfig` file that lists them and can be chain loaded from `~/.mrconfig`. * Manage your whole home directory in version control. (See [VCS-Home](http://vcs-home.branchable.com/)) ## news [[!inline pages="news/* and !*/Discussion" show="4" archive=yes]] myrepos/doc/todo/0000755000000000000000000000000012165610140011150 5ustar myrepos/doc/todo/smarter_chained_checkout.mdwn0000644000000000000000000000053312165610061017057 0ustar 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) myrepos/doc/todo/done.mdwn0000644000000000000000000000017212165606604012776 0ustar recently fixed [[todo]] items. [[!inline pages="./* and link(./done) and !*/Discussion" sort=mtime show=10 archive=yes]] myrepos/doc/todo/detect_unregistered_repos_in_tree.mdwn0000644000000000000000000000021712165610140021004 0ustar 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!) myrepos/doc/todo.mdwn0000644000000000000000000000036512165606564012062 0ustar This is myrepos's todo list. Link items to [[todo/done]] when done. See also: [Debian BTS](http://bugs.debian.org/mr). [[!inline pages="./todo/* and !./todo/done and !link(done) and !*/Discussion" actions=yes postform=yes show=0 archive=yes]] myrepos/doc/install.mdwn0000644000000000000000000000044512165607545012562 0ustar `git clone git://myrepos.branchable.com/ myrepos` Or get it [from github](https://github.com/joeyh/myrepos). It's a simple perl script, which can be copied anywhere to install. Myrepos is included in all recent versions of Debian. `apt-get install mr` It's also in Mac Homebrew as `mr`. myrepos/doc/sidebar.mdwn0000644000000000000000000000032012165607305012507 0ustar * [[install]] * [[todo]] * Flattr this myrepos/GPL0000644000000000000000000004310312165601744010016 0ustar 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. Copyright (C) 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. , 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. myrepos/webcheckout0000755000000000000000000001375012165606073011707 0ustar #!/usr/bin/perl =head1 NAME webcheckout - check out repositories referenced on a web page =head1 SYNOPSIS B [options] url [destdir] =head1 DESCRIPTION B 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 . 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 Licensed under the GNU GPL version 2 or higher. This program is included in myrepos =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 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); myrepos/lib/0000755000000000000000000000000012165601744010216 5ustar myrepos/lib/git-fake-bare0000644000000000000000000000520112165601744012535 0ustar # 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 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 myrepos/lib/git-svn0000644000000000000000000000242412165601744011532 0ustar # 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 myrepos/lib/vcsh0000644000000000000000000000060512165601744011105 0ustar # 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 myrepos/lib/git-subtree0000644000000000000000000000227212165601744012376 0ustar # 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 . myrepos/lib/vis0000644000000000000000000000053112165601744010741 0ustar # 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 myrepos/lib/unison0000644000000000000000000000206512165601744011457 0ustar # 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 myrepos/lib/repo0000644000000000000000000000060312165601744011105 0ustar # 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 "$@" myrepos/mrconfig0000644000000000000000000000056512165601744011205 0ustar # 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 myrepos/mr0000755000000000000000000015221712204457024010016 0ustar #!/usr/bin/perl =head1 NAME mr - a tool to manage all your version control repos =head1 SYNOPSIS B [options] checkout B [options] update B [options] status B [options] commit [-m "message"] B [options] record [-m "message"] B [options] fetch B [options] push B [options] diff B [options] log B [options] grep pattern B [options] run command [param ...] B [options] bootstrap url [directory] B [options] register [repository] B [options] config section ["parameter=[value]" ...] B [options] action [params ...] B [options] [online|offline] B [options] remember action [params ...] =head1 DESCRIPTION B is a tool to manage all your version control repos. 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 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 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 grep pattern Searches for a pattern in each repository using the grep subcommand. Uses ack-grep on VCS that do not have their own. =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 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 section allows setting default values for the sections that come after it. The C 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, C, C or C (for a bound branch), etc. Note that these shell commands are run in a C 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 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 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 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 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 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 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 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. 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 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 () { 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: myrepos/.gitignore0000644000000000000000000000012712165601744011440 0ustar debian/files debian/mr.debhelper.log debian/mr.substvars debian/mr/ mr.1 webcheckout.1 myrepos/.gitattributes0000644000000000000000000000005412165601744012342 0ustar debian/changelog merge=dpkg-mergechangelogs