From 4cda4e5c360d4cd0cd33ef8b3bd2cd55eb50b583 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 10 Oct 2007 23:43:14 -0400 Subject: [PATCH 1/1] initial checkin --- debian/changelog | 5 + debian/compat | 1 + debian/control | 19 +++ debian/copyright | 5 + debian/rules | 31 +++++ mr | 309 +++++++++++++++++++++++++++++++++++++++++++++++ mrconfig | 25 ++++ 7 files changed, 395 insertions(+) create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100755 debian/rules create mode 100755 mr create mode 100644 mrconfig diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..88bf521 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +mr (0.1) unstable; urgency=low + + * First release. + + -- Joey Hess Wed, 10 Oct 2007 22:37:19 -0400 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..eb3754a --- /dev/null +++ b/debian/control @@ -0,0 +1,19 @@ +Source: mr +Section: utils +Priority: optional +Build-Depends: debhelper (>= 5), dpkg-dev (>= 1.9.0) +Maintainer: Joey Hess +Standards-Version: 3.7.2 + +Package: mr +Architecture: all +Section: utils +Depends: +Suggests: subversion, git-core +Description: a Multiple Repository management tool + The mr(1) command allows you to register a set of repositories in + a .mrconfig file, and then checkout, update, or perform other actions on + all of the repositories at once. + . + Any mix of revision control systems can be used with mr(1), and you can + define arbitrary actions like "update", "checkout", or "commit". diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..24c49f0 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,5 @@ +Files: * +Copyright: (c) 2007 Joey Hess +License: GPL-2+ + On Debian systems, the complete text of the GPL can be found in + /usr/share/common-licenses/GPL. diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..cb8604b --- /dev/null +++ b/debian/rules @@ -0,0 +1,31 @@ +#!/usr/bin/make -f + +build: + pod2man -c mr mr > mr.1 + +clean: + dh_testdir + dh_testroot + dh_clean mr.1 + +binary-arch: build + +binary-indep: build + dh_testdir + dh_testroot + dh_clean -k + dh_install mr usr/bin + dh_installdocs + dh_installexamples mrconfig + dh_installman *.1 + dh_installchangelogs + dh_compress + dh_fixperms + dh_perl + dh_installdeb + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary diff --git a/mr b/mr new file mode 100755 index 0000000..aa15728 --- /dev/null +++ b/mr @@ -0,0 +1,309 @@ +#!/usr/bin/perl + +=head1 NAME + +mr - a Multiple Repository management tool + +=head1 SYNOPSIS + +B [options] checkout + +B [options] update + +B [options] status + +B [options] commit -m "message" + +B [options] action [params ...] + +=head1 DESCRIPTION + +B is a Multiple Repository management tool. It allows you to register a +set of repositories in a .mrconfig file, and then checkout, update, or +perform other actions on all of the repositories at once. + +Any mix of revision control systems can be used with B, and you can +define arbitrary actions like "update", "checkout", or "commit". + +=head1 OPTIONS + +=over 4 + +=item -d directory + +Specifies the topmost directory that B should work in. The default is +the current working directory. B will operate on all registered +repositories at or under the directory. + +=item -c mrconfig + +Use the specified mrconfig file, instead of looking for on in your home +directory. + +=item -v + +Be verbose. + +=back + +=head1 FILES + +B is configured by .mrconfig files. It searches for .mrconfig files in +your home directory, and in the root directory of each repository specified +in a .mrconfig file. So you could have a ~/.mrconfig that registers a +repository ~/src, that itself contains a ~/src/.mrconfig file, that in turn +registers several additional repositories. + +The .mrconfig file uses a variant of the INI file format. Lines starting with +"#" are comments. Lines ending with "\" are continued on to the next line. +Sections specify where each repository is located, relative to the +directory that contains the .mrconfig file. + +Within a section, each parameter defines a shell command to run to handle a +given action. Note that these shell commands are run in a "set -e" shell +environment, and B cds into the repository directory before running +them, except for the "checkout" command, which is run in the parent of the +repository directory, since the repository isn't checked out yet. + +There are two special parameters. If the "skip" parameter is set and +its command returns nonzero, then B will skip acting on that repository. + +The "default" section allows setting up default handlers for each action, +and is overridden by the contents of other sections. mr contains default +handlers for the "update", "status", and "commit" actions, so normally +you only need to specify what to do for "checkout". + +For example: + + [src] + checkout = svn co svn://svn.example.com/src/trunk src + + [src/linux-2.6] + # only check this out on kodama + skip = test $(hostname) != kodama + checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git + +=head1 AUTHOR + +Copyright 2007 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); + +my $directory=getcwd(); +my $config="$ENV{HOME}/.mrconfig"; +my $verbose=0; +my %config; + +Getopt::Long::Configure("no_permute"); +my $result=GetOptions( + "d=s" => sub { $directory=abs_path($_[1]) }, + "c=s" => \$config, + "v" => \$verbose, +); +if (! $result || @ARGV < 1) { + die("Usage: mr [-d directory] action [params ...]\n"); +} +my $action=shift @ARGV; + +loadconfig(\*DATA); +loadconfig($config); +#use Data::Dumper; +#print Dumper(\%config); + +my (@failures, @successes, @skipped); +my $first=1; +foreach my $topdir (sort keys %config) { + foreach my $subdir (sort keys %{$config{$topdir}}) { + next if $subdir eq 'default'; + + my $dir=$topdir.$subdir; + + if (defined $directory && + $dir !~ /^\Q$directory\E\//) { + print "mr $action: $dir skipped per -d parameter\n" if $verbose; + push @skipped, $dir; + next; + } + + print "\n" unless $first; + $first=0; + + if (exists $config{$topdir}{$subdir}{skip}) { + my $ret=system($config{$topdir}{$subdir}{skip}); + if ($ret >> 8 == 0) { + print "mr $action: $dir skipped per config file\n" if $verbose; + push @skipped, $dir; + next; + } + } + + if ($action eq 'checkout') { + if (-e $dir) { + print "mr $action: $dir already exists, skipping checkout\n"; + push @skipped, $dir; + next; + } + $dir=~s/^(.*)\/[^\/]+\/?$/$1/; + } + if (! chdir($dir)) { + print STDERR "mr $action: failed to chdir to $dir: $!\n"; + push @skipped, $dir; + } + elsif (! exists $config{$topdir}{$subdir}{$action}) { + print STDERR "mr $action: no defined $action command for $topdir$subdir, skipping\n"; + push @skipped, $dir; + } + else { + print "mr $action: in $dir\n"; + my $command="set -e; my_action(){ $config{$topdir}{$subdir}{$action} ; }; my_action @ARGV"; + my $ret=system($command); + if ($ret != 0) { + print STDERR "mr $action: failed to run: $command\n" if $verbose; + push @failures, $topdir.$subdir; + if ($ret >> 8 != 0) { + print STDERR "mr $action: command failed\n"; + } + elsif ($ret != 0) { + print STDERR "mr $action: command died ($ret)\n"; + } + } + else { + push @successes, $dir; + } + } + } +} + +sub showstat { + my $count=shift; + my $singular=shift; + my $plural=shift; + if ($count) { + return "$count ".($count > 1 ? $plural : $singular); + } + return; +} +print "\nmr $action: finished (".join("; ", + showstat($#successes+1, "success", "sucesses"), + showstat($#failures+1, "failure", "failures"), + showstat($#skipped+1, "skipped", "skipped"), +).")\n"; +exit @failures ? 1 : 0; + +my %loaded; +sub loadconfig { + my $f=shift; + + my @toload; + + my $in; + my $dir; + if (ref $f eq 'GLOB') { + $in=$f; + $dir=""; + } + else { + $f=abs_path($f); + + if ($loaded{$f}) { + return; + } + $loaded{$f}=1; + + print "mr: loading config $f\n" if $verbose; + open($in, "<", $f) || die "mr: open $f: $!\n"; + ($dir)=$f=~/^(.*\/)[^\/]+$/; + + # copy in defaults from first parent + my $parent=$dir; + while ($parent=~s/^(.*)\/[^\/]+\/?$/$1/) { + if (exists $config{$parent} && + exists $config{$parent}{default}) { + $config{$dir}{default}={ %{$config{$parent}{default}} }; + last; + } + } + } + + my $section; + while (<$in>) { + chomp; + next if /^\s*\#/ || /^\s*$/; + if (/^\s*\[([^\]]*)\]\s*$/) { + $section=$1; + if (length $dir && $section ne "default" && + -e $dir.$section."/.mrconfig") { + push @toload, $dir.$section."/.mrconfig"; + } + } + elsif (/^\s*(\w+)\s*=\s*(.*)/) { + my $parameter=$1; + my $value=$2; + + # continuation line + while ($value=~/(.*)\\$/) { + $value=$1.<$in>; + chomp $value; + } + + if (! defined $section) { + die "$f line $.: parameter ($parameter) not in section\n"; + } + if (! exists $config{$dir}{$section} && + exists $config{$dir}{default}) { + # copy in defaults + $config{$dir}{$section}={ %{$config{$dir}{default}} }; + } + $config{$dir}{$section}{$parameter}=$value; + } + else { + die "$f line $.: parse error\n"; + } + } + close $in; + + foreach (@toload) { + loadconfig($_); + } +} + +__DATA__ +# Some useful actions that mr knows about by default. +# These can be overridden in ~/.mrconfig. +[default] +update = \ + if [ -d .svn ]; then \ + svn update; \ + elif [ -d .git ]; then \ + git pull origin master; \ + else \ + echo "mr update: unknown RCS"; \ + exit 1; \ + fi +status = \ + if [ -d .svn ]; then \ + svn status; \ + elif [ -d .git ]; then \ + git status || true; \ + else \ + echo "mr status: unknown RCS"; \ + exit 1; \ + fi +commit = \ + if [ -d .svn ]; then \ + svn commit "$@"; \ + elif [ -d .git ]; then \ + git commit -a "$@"; \ + else \ + echo "mr commit: unknown RCS"; \ + exit 1; \ + fi diff --git a/mrconfig b/mrconfig new file mode 100644 index 0000000..aff6a91 --- /dev/null +++ b/mrconfig @@ -0,0 +1,25 @@ +# An example config file for the mr(1) command. + +[src/mr] +checkout = git clone ssh://kitenet.net/srv/git/kitenet.net/mr + +[src/linux-2.6] +checkout = git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git +# I only check this out on kodama, otherwise skip it. +skip = test $(hostname) != kodama + +[src/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://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 + +# My home directory, which I keep in svn. +[] +# Since I have a lot of different branches for different machines, +# the checkout procedure is manual. +checkout = echo "run svn+ssh://svn.kitenet.net/srv/svn/joey/trunk/home- ."; \ + exit 1 -- 2.39.5