various suggestions for mr

Adam Spiers vcs-home at adamspiers.org
Fri Oct 28 13:24:02 CEST 2011


Hi all,

I've been tracking my dot files and related stuff since around 1999,
and was very excited to discover this mailing list two years ago.
Since then I've only been able to lurk, but finally have a bit of
spare time to participate.

Since 2005 I have been using my own (as yet unpublished) Perl suite
called "cfgctl" to manage my dot files, which serves a similar to
purpose to mr - applying operations such as "checkout", "update"
etc. to multiple repositories using multiple VCS backends.  However
its shortcomings have increasingly irked me, and just when I was being
to resign myself to a complete rewrite, I noticed that mr had already
stolen most of my thunder :-)

The main differences between mr and cfgctl are:

  - cfgctl's repository meta-data is written in pure Perl (and then
    require'd at run-time) rather than one or more .INI-style
    .mrconfig files.  When I originally wrote it, needing
    functionality akin to mr's "skip" parameter I naively thought that
    this would give me maximum flexibility, and I mistakenly
    underestimated the value of using a DSL.

  - cfgctl only has a fixup hook (i.e. post-checkout/update), and no
    hooks for any other actions.

  - cfgctl is not nearly as extensible.  It doesn't support writing
    arbitrary new actions either globally or per repo.

  - With hindsight cfgctl should have been made more shell-oriented in
    general, because the kinds of tasks required for managing dot
    files/repos, building/install packages etc. are much more easily
    achieved in shell than in Perl.

  - Adding support for a new VCS backend is more work in cfgctl,
    because it requires writing a new Perl subclass of Cfg::Pkg::Base.

  - cfgctl is harder to type than 'mr'.

So far mr is clearly winning :-)  However, cfgctl does have one or two
tricks up its sleeve:

  - Config modules / packages / repositories / whatever you want to
    call them are indexed by name within a unique namespace, rather
    than by directory path, and packages are grouped together into
    sections.  This allows you to easily run any of the actions on:

      - all the packages (just like running mr from $HOME)

      - a single package, just by specifying its name without needing
        to know where it lives, e.g. "cfgctl --update zsh" would
        update just the zsh repository

      - a section (i.e. group of packages) just by specifying its name
        (e.g. "CLI" or "mail" or "Xorg") without needing to know where
        anything lives, e.g. "cfgctl --pull Xorg" would update all
        repos containing config relating to my Xorg (previously X11)
        environment

      - any packages matching a regular expression e.g.
        "cfgctl --update /emacs/"

  - cfgctl integrates out of the box with GNU stow.  Based on this
    list's frequency of discussion on "fake bare" / detached git
    repos, it seems that most people here have an aversion to the
    symlink approach to managing dot files in $HOME.  This surprises
    me as I have been using them very successfully for 12 years,
    although I will leave that debate for another thread.

  - cfgctl's internals have a reasonable amount of pod / comments.
    mr's code, whilst mostly self-explanatory at the high-level, does
    have a few very long subroutines which I feel would gain clarity
    by being refactored into some appropriately-named smaller subs.

All in all, I feel that mr has a better design than cfgctl, and has
greater longevity.  So last night I spent an hour or two doing a quick
proof of concept, to see whether I could extend mr to implement the
functionality I require, in particular the integration with GNU stow.
I'm pleased to say that so far it's looking very promising :-)
This is pretty much all that's needed:

--------- 8< --------- 8< --------- 8< --------- 8< --------- 8< ---------
[DEFAULT]
lib =
    STOW_DIR=$HOME/.cfg
    STOW_TARGET=$HOME
    MR_NAME="`basename $MR_REPO`"
    #
    set_stow_common_opts () {
        STOW_PKG_PATH="$STOW_DIR/$MR_NAME"
        stow_common_opts="-t $STOW_TARGET -d $STOW_DIR"
    }
    #
    install () {
        set_stow_common_opts
        ensure_symlink_exists "$STOW_PKG_PATH" "${MR_REPO%/}"
    }
    #
    ensure_symlink_exists () {
        [ $# = 2 ] || error "CONFIG BUG: Usage: ensure_symlink_exists
SYMLINK TARGET"
        symlink="$1"
        required_target="$2"
        if [ -L "$symlink" ]; then
            actual_target="`readlink $symlink`"
            if [ "$actual_target" = "$required_target" ]; then
                return
            else
                error "Symlink $symlink already points to
$actual_target, cannot point to $required_target; aborting."
            fi
        fi
        [ -e "$symlink" ] && error "Cannot create symlink $symlink -
already exists; aborting."
        ln -s "$required_target" "$symlink"
    }
    #
    stow () {
        set_stow_common_opts
        command stow $stow_common_opts "$MR_NAME"
    }
    restow () {
        set_stow_common_opts
        command stow -R -p $stow_common_opts "$MR_NAME"
    }
    unstow () {
        set_stow_common_opts
        command stow -D -p $stow_common_opts "$MR_NAME"
    }

post_checkout = install && stow
post_update = install && restow
unstow = unstow
--------- 8< --------- 8< --------- 8< --------- 8< --------- 8< ---------

However, I am already beginning to miss the lack of a native namespace
for repo identifiers.  For example, I would far rather type

    mr update zsh emacs

than

    mr -c ~/.config/mr/config.d/zsh update
    mr -c ~/.config/mr/config.d/emacs update

If CLI parameters following the action verb are already reserved for
being passed on to the underlying VCS backend, then something like
this might be appropriate:

    mr -f zsh,emacs update

The identifier could be declared simply in the config via a special
'name' parameter.  The following example shows one of the situations
in which you might need the name to differ from the basename of the
repository path ($MR_REPO).

--------- 8< --------- 8< --------- 8< --------- 8< --------- 8< ---------
[$HOME/.git-repos/zsh]
name = zsh.git

[$HOME/.cvs-working-dirs/zsh]
name = zsh.cvs
--------- 8< --------- 8< --------- 8< --------- 8< --------- 8< ---------

Similar functionality for grouping multiple repos together could be achieved
by something like:

--------- 8< --------- 8< --------- 8< --------- 8< --------- 8< ---------
[$HOME/.config/mr/groups.d/CLI]
checkout = true
include = cat ~/.config/mr/config.d/{zsh,emacs}
--------- 8< --------- 8< --------- 8< --------- 8< --------- 8< ---------

invoked via

    mr -c ~/.config/mr/groups.d/CLI update

although that's kind of ugly.  Native support would be nicer, e.g.

    mr -g CLI update

I'd love to hear anyone's thoughts on these suggestions!

Cheers,
Adam


More information about the vcs-home mailing list