diff --git a/doc/vcsh.1.ronn.in b/doc/vcsh.1.ronn.in index 2300989e..cbd4e8db 100644 --- a/doc/vcsh.1.ronn.in +++ b/doc/vcsh.1.ronn.in @@ -5,6 +5,8 @@ vcsh(1) - Version Control System for $HOME - multiple Git repositories in $HOME `@TRANSFORMED_PACKAGE_NAME@` [] +`@TRANSFORMED_PACKAGE_NAME@` alias [-d] [[=]] + `@TRANSFORMED_PACKAGE_NAME@` clone [-b ] [] `@TRANSFORMED_PACKAGE_NAME@` delete @@ -82,6 +84,15 @@ an interactive user. ## COMMANDS +* alias: + List all aliases in <$XDG_CONFIG_HOME/vcsh/aliases>. + + ``: Print the right side of alias definition. + + `=`: Add alias. Make backup in <$XDG_CONFIG_HOME/vcsh/aliases.bak>. + + `-d `: Delete alias. Make backup in <$XDG_CONFIG_HOME/vcsh/aliases.bak>. + * clone: Clone an existing repository. @@ -184,6 +195,17 @@ an interactive user. As noted earlier, `@TRANSFORMED_PACKAGE_NAME@` will set <$GIT_DIR> and <$GIT_WORK_TREE> to the appropriate values for fake bare Git repositories. +## ALIASES + +`@TRANSFORMED_PACKAGE_NAME@` allows to define aliases. The first existing file + +* <$XDG_CONFIG_HOME/vcsh/aliases> +* + +will be read. An alias definition has the format `alias = command`. +Empty lines and lines starting with '#' are ignored. + + ## CONFIG There are several ways to turn the various knobs on `@TRANSFORMED_PACKAGE_NAME@`. In order of diff --git a/t/800-aliases.t b/t/800-aliases.t new file mode 100644 index 00000000..6cb250b9 --- /dev/null +++ b/t/800-aliases.t @@ -0,0 +1,47 @@ +#!/usr/bin/perl +# +BEGIN { $ENV{LC_ALL} = 'C' } + +use strict; +use warnings; + +use Cwd 'abs_path'; +use Test::Most; + +chdir 't/etc/' or die $!; + +$ENV{'HOME'} = abs_path ('.vcsh_home'); + +chdir '.vcsh_home' or die $!; + +my $output = `../vcsh alias`; +ok $output eq "", 'No aliases set up yet.'; + +system("../vcsh alias ls=list"); +$output = `../vcsh alias`; +ok $output eq "ls = list\n", 'Add alias ls'; + +system("../vcsh alias ci=commit -a"); +$output = `../vcsh alias`; +ok $output eq "ls = list +ci = commit -a +", 'Add alias ci'; + +system("../vcsh alias co=upgrade"); +$output = `../vcsh alias`; +ok $output eq "ls = list +ci = commit -a +co = upgrade +", 'Add alias co'; + +$output = `../vcsh alias ci`; +ok $output eq "commit -a\n", 'Get alias ci'; + +system("../vcsh alias -d ci"); +$output = `../vcsh alias`; +ok $output eq "ls = list +co = upgrade +", 'Delete alias ci'; + +done_testing; + diff --git a/vcsh.in b/vcsh.in index 4b2c70a2..da923db7 100755 --- a/vcsh.in +++ b/vcsh.in @@ -115,6 +115,7 @@ help() { -v Enable verbose mode commands: + alias [] List defined aliases clone [-b ] \\ \\ [] Clone from an existing repository @@ -170,6 +171,78 @@ info() { echo "$VCSH_SELF: info: $1" } +alias_read() { + local aliases + if [ -r "$XDG_CONFIG_HOME/vcsh/aliases" ]; then + aliases="$XDG_CONFIG_HOME/vcsh/aliases" + elif [ -r /etc/vcsh/aliases ]; then + aliases=/etc/vcsh/aliases + else + return + fi + sed -r -e 's/#.*//' -ne 's/(\w+)\s*=\s*(.+)/\1 \2/p' "$aliases" +} + +alias_get() { + local a cmd + if [ -n "$1" ]; then + alias_read | while read a cmd; do + if [ x"$1" = x"$a" ]; then + echo $cmd + return + fi + done + fi +} + +alias_write() { + [ -w "$XDG_CONFIG_HOME/vcsh/aliases" ] || + [ ! -e "$XDG_CONFIG_HOME/vcsh/aliases" -a -w "$XDG_CONFIG_HOME/vcsh/" ] || + fatal "File '$XDG_CONFIG_HOME/vcsh/aliases' not writeable" + [ "$2" = '=' ] || fatal 'Invalid alias format' + if [ -n "$(alias_get $1)" ]; then + local regex="s/^$1\s*=.*/$@/" + sed -i.bak -re "$regex" "$XDG_CONFIG_HOME/vcsh/aliases" + else + echo "$@" >> "$XDG_CONFIG_HOME/vcsh/aliases" + fi +} + +alias_remove() { + [ -w "$XDG_CONFIG_HOME/vcsh/aliases" ] || + [ ! -e "$XDG_CONFIG_HOME/vcsh/aliases" -a -w "$XDG_CONFIG_HOME/vcsh/" ] || + fatal "File '$XDG_CONFIG_HOME/vcsh/aliases' not writeable" + [ -n "$1" ] || fatal 'Empty alias' + local regex="/^$1\s*=/d" + sed -i.bak -re "$regex" "$XDG_CONFIG_HOME/vcsh/aliases" +} + +aliases() { + if [ -n "$1" ]; then + local opts subcmd + while getopts d: opts; do + if [ $opts = d ]; then + alias_remove "$OPTARG" + fi + done + shift $(($OPTIND - 1)) + local alias="$(echo "$@" | sed -nre 's/(\w+)\s*=\s*(\w.*)/\1 = \2/p')" + if [ -n "$alias" ]; then + alias_write $alias + else + alias_get "$1" + fi + else + # print all aliases + local a cmd + alias_read | while read a cmd; do + if [ -n "$a" ]; then + echo $a = $cmd + fi + done + fi +} + clone() { hook pre-clone # Check if remote is reachable. Abort early if there's a typo, TLS certificate problem, etc @@ -429,6 +502,7 @@ push() { hook_global post-push } + retire() { unset VCSH_DIRECTORY } @@ -595,23 +669,32 @@ fi VCSH_COMMAND=$1; export VCSH_COMMAND -case $VCSH_COMMAND in - clon|clo|cl) VCSH_COMMAND=clone;; - commi|comm|com|co|ci) VCSH_COMMAND=commit;; - delet|dele|del|de) VCSH_COMMAND=delete;; - ente|ent|en) VCSH_COMMAND=enter;; - hel|he) VCSH_COMMAND=help;; - ini|in) VCSH_COMMAND=init;; - pul) VCSH_COMMAND=pull;; - pus) VCSH_COMMAND=push;; - renam|rena|ren|re) VCSH_COMMAND=rename;; - ru) VCSH_COMMAND=run;; - statu|stat|sta|st) VCSH_COMMAND=status;; - upgrad|upgra|upgr|up) VCSH_COMMAND=upgrade;; - versio|versi|vers|ver|ve) VCSH_COMMAND=version;; - which|whi|wh) VCSH_COMMAND=which;; - write|writ|wri|wr) VCSH_COMMAND=write-gitignore;; -esac +alias="$(alias_get $VCSH_COMMAND)" +if [ -n "$alias" ]; then + VCSH_COMMAND="$alias" +else + case $VCSH_COMMAND in + clon|clo|cl) old_alias=1;; + commi|comm|com|co|ci) old_alias=1;; + delet|dele|del|de) old_alias=1;; + ente|ent|en) old_alias=1;; + hel|he) old_alias=1;; + ini|in) old_alias=1;; + pul) old_alias=1;; + pus) old_alias=1;; + renam|rena|ren|re) old_alias=1;; + ru) old_alias=1;; + statu|stat|sta|st) old_alias=1;; + upgrad|upgra|upgr|up) old_alias=1;; + versio|versi|vers|ver|ve) old_alias=1;; + which|whi|wh) old_alias=1;; + write|writ|wri|wr) old_alias=1;; + esac + if [ -n "$old_alias" ]; then + echo "Aliases now dynamically defined. See vcsh(1) for details." + exit 1 + fi +fi if [ x"$VCSH_COMMAND" = x'clone' ]; then VCSH_BRANCH= @@ -682,6 +765,10 @@ elif [ x"$VCSH_COMMAND" = x'status' ]; then shift fi VCSH_REPO_NAME=$2; export VCSH_REPO_NAME +elif [ x"$VCSH_COMMAND" = x'alias' ]; then + shift + aliases $@ + exit elif [ -n "$2" ]; then VCSH_COMMAND='run'; export VCSH_COMMAND VCSH_REPO_NAME=$1; export VCSH_REPO_NAME