diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 07456f9c7b4caa89ae1fc76906ccbf712990db17..d7a3394767a47d8d5d865a9ed2fb422797d7f438 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -6,6 +6,7 @@ Eric Bouchut <ebouchut@gmail.com> Dridi Boukelmoune <dridi.boukelmoune@gmail.com> Rob Cornish <jrmcornish@gmail.com> Vincent Demeester <vincent@demeester.fr> +Mert Dirik <mertdirik@gmail.com> Jeff Fein-Worton <jeff@fein-worton.com> Thomas Ferris Nicolaisen <tfnico@gmail.com> martin f. krafft <madduck@madduck.net> @@ -17,6 +18,7 @@ Valentin Haenel <valentin.haenel@gmx.de> Richard Hartmann <richih@debian.org> Gregor Jasny <gjasny@googlemail.com> Errietta Kostala <errietta@errietta.me> +Yuval Langer <yuval.langer@gmail.com> Caleb Maclennan <caleb@alerque.com> Markus Martin <markus@archwyrm.net> mek-apelsin <mek@pels.in> @@ -26,10 +28,12 @@ Corey Quinn <corey@sequestered.net> Pavlos Ratis <dastergon@gentoo.org> Dewey Sasser <dewey@sasser.com> Gernot Schulz <post@gernot-schulz.com> +Aaron Schumacher <ajschumacher@gmail.com> Andrew Schwartzmeyer <andrew@schwartzmeyer.com> Dato Simó <dato@net.com.org.es> Alexander Skurikhin <a.skurihin@gmail.com> Jonathan Sternberg <jonathansternberg@gmail.com> Frank Terbeck <ft@bewatermyfriend.org> +mirabilos <tg@debian.org> Aaron VonderHaar <gruen0aermel@gmail.com> Tony <zearin@gonk.net> diff --git a/Makefile b/Makefile index 9e3d5a22c5710325afb465891dbbb6a1f69937b3..ca6ebe681a99ac4f85d78d4ccd0974d4b4d7d413 100644 --- a/Makefile +++ b/Makefile @@ -47,4 +47,4 @@ test: @if which git > /dev/null ; then :; else echo "'git' not found, exiting..."; exit 1; fi moo: - @if [ -x /usr/games/cowsay ]; then /usr/games/cowsay "I hope you're happy now..."; fi + @ which cowsay >/dev/null 2>&1 && cowsay "I hope you're happy now..." diff --git a/_vcsh b/_vcsh index a33551bece27c8fe84f8ede4a94eedf0c2492d3d..53eaada4dae3764f4ff63bb88e5e68c80ae11393 100644 --- a/_vcsh +++ b/_vcsh @@ -43,6 +43,10 @@ function _vcsh-list-tracked-by () { (( CURRENT == 2 )) && __vcsh_repositories } +function _vcsh-list-untracked () { + _nothing +} + function _vcsh-pull () { _nothing } @@ -66,6 +70,10 @@ function _vcsh-run () { fi } +function _vcsh-status () { + (( CURRENT == 2 )) && __vcsh_repositories +} + function _vcsh-upgrade () { (( CURRENT == 2 )) && __vcsh_repositories } @@ -97,6 +105,7 @@ function _vcsh () { "list:list all local vcsh repositories" "list-tracked:list all files tracked by vcsh" "list-tracked-by:list files tracked by a repository" + "list-untracked:list all files not tracked by vcsh" "pull:pull from all vcsh remotes" "push:push to vcsh remotes" "rename:rename a repository" diff --git a/changelog b/changelog index 1c09b670bac1b13df74eefb459f8153c39f23af1..badf445740c2597e012e55dcbbfb0992b3782882 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,17 @@ +2014-10-25 Richard Hartmann <richih.mailinglist@gmail.com> + + * Release 1.20141025 + * `vcsh which dontexist` exits 1 + * `vcsh status` shows commits ahead/behind remote tracking branch + * Support overlay functions + * Support `vcsh list-untracked`, optionally recursively + * Support `vcsh list-untracked $repo` + * Improve error handling of clone() + * Rename `list-tracked-by` to `list-tracked <repo>` + * Support repo-specific config files + * Various minor improvements + * More moo + 2014-10-09 Richard Hartmann <richih.mailinglist@gmail.com> * Display full paths in list-tracked* diff --git a/doc/vcsh.1.ronn b/doc/vcsh.1.ronn index 05da4fad8bfe18dce47ab8aaa2c3dc9f9c619275..c91f6466e5b0706cdedeb1864aa6d3e5b9f5a517 100644 --- a/doc/vcsh.1.ronn +++ b/doc/vcsh.1.ronn @@ -17,9 +17,9 @@ vcsh(1) - Version Control System for $HOME - multiple Git repositories in $HOME `vcsh` list -`vcsh` list-tracked +`vcsh` list-tracked [<rpoe>] -`vcsh` list-tracked-by <repo> +`vcsh` list-untracked [<-r>] [<repo>] `vcsh` pull @@ -107,9 +107,24 @@ an interactive user. * list-tracked: List all files tracked by vcsh. + If you want to list files tracked by a specific repository, simply + append the repository's name last. + * list-tracked-by: List files tracked by a repository. + This is a legacy command; you should use `list-tracked <repo>` instead. + +* list-untracked: + List all files NOT tracked by vcsh. + + By default, the file list is shallow and stops at directory levels where + possible. If you prefer to get a list of all files, append `-r` for + recursive mode. + + If you want to list files not tracked by a specific repository, simply + append the repository's name last. + * pull: Pull from all vcsh remotes. @@ -254,6 +269,24 @@ Available hooks are <pre-clone>, <post-clone>, <post-clone-retired>, If you need more, vcsh is trivial to patch, but please let upstream know so we can ship them by default. +## OVERLAY SYSTEM + +`vcsh` also provides an overlay system. Similar to hooks, the recommended +locations are <$XDG_CONFIG_HOME/vcsh/overlays-available> and +<$XDG_CONFIG_HOME/vcsh/overlays-enabled>. + +Overlays follow the same rules as hooks and you are free to overwrite any +and all functions. Same as hooks, you can use global or repository-specific +overlays by using either <$VCSH_OVERLAY_D/$VCSH_COMMAND> or +<$VCSH_OVERLAY_D/$VCSH_REPO_NAME.$VCSH_COMMAND>. + +Please note that nothing stops you from, e.g. overwriting `status()` in +<$VCSH_OVERLAY_D/commit>. As the overlays will be sourced and you are +replacing arbitrary functions, any and all features may stop working, or you +may even lose data. + +You have been warned. + ## DETAILED HOWTO AND FURTHER READING Manpages are often short and sometimes useless to glean best practices from. diff --git a/vcsh b/vcsh index 6a81343cf8ec188b1a8aeda2fe6495afb51cd85a..cf7d1a7178d71ce1f34c46aaf09838ae7ac7c459 100755 --- a/vcsh +++ b/vcsh @@ -19,11 +19,12 @@ # If '.git-HEAD' is appended to the version, you are seeing an unreleased # version of vcsh; the master branch is supposed to be clean at all times # so you can most likely just use it nonetheless -VERSION='1.20141009' +VERSION='1.20141025' SELF=$(basename $0) fatal() { echo "$SELF: fatal: $1" >&2 + [ -z $2] && exit 1 exit $2 } @@ -75,6 +76,7 @@ fi # Read defaults : ${VCSH_REPO_D:="$XDG_CONFIG_HOME/vcsh/repo.d"} : ${VCSH_HOOK_D:="$XDG_CONFIG_HOME/vcsh/hooks-enabled"} +: ${VCSH_OVERLAY_D:="$XDG_CONFIG_HOME/vcsh/overlays-enabled"} : ${VCSH_BASE:="$HOME"} : ${VCSH_GITIGNORE:=exact} : ${VCSH_GITATTRIBUTES:=none} @@ -106,9 +108,10 @@ help() { help Display this help text init <repo> Initialize a new repository list List all repositories - list-tracked List all files tracked by vcsh - list-tracked-by \\ - <repo> List files tracked by a repository + list-tracked \\ + [<repo>] List all files tracked all or one repositories + list-untracked \\ + [<-r>] [<repo>] List all files not tracked by all or one repositories pull Pull from all vcsh remotes push Push to vcsh remotes rename <repo> \\ @@ -148,9 +151,10 @@ clone() { git remote add origin "$GIT_REMOTE" git config branch.master.remote origin git config branch.master.merge refs/heads/master - if [ $(git ls-remote origin master 2> /dev/null | wc -l ) -lt 1 ]; then - info "remote is empty, not merging anything" - exit + VCSH_CLONE_ERROR=$(git ls-remote origin master 2>&1) + if [ -n "$VCSH_CLONE_ERROR" ]; then + rm -rf "$GIT_DIR" + fatal "$VCSH_CLONE_ERROR" 1 fi git fetch hook pre-merge @@ -242,16 +246,71 @@ get_files() { } list_tracked() { - for VCSH_REPO_NAME in $(list); do - get_files - done | sed "s,^,$(printf '%s\n' "$VCSH_BASE/" | \ - sed 's/[,\&]/\\&/g')," | sort -u + VCSH_REPO_NAME=$2; export VCSH_REPO_NAME + if [ -n "$VCSH_REPO_NAME" ]; then + get_files | list_tracked_helper + else + for VCSH_REPO_NAME in $(list); do + get_files + done | list_tracked_helper + fi +} + +list_tracked_helper() { + sed "s,^,$(printf '%s\n' "$VCSH_BASE/" | sed 's/[,\&]/\\&/g')," | sort -u } list_tracked_by() { - use - git ls-files | sed "s,^,$(printf '%s\n' "$VCSH_BASE/" | \ - sed 's/[,\&]/\\&/g')," | sort -u + list_tracked $2 +} + +list_untracked() { + command -v 'comm' >/dev/null 2>&1 || fatal "Could not find 'comm'" + + temp_file_others=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file' + temp_file_untracked=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file' + temp_file_untracked_copy=$(mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX") || fatal 'Could not create temp file' + + # Hack in support for `vcsh list-untracked -r`... + directory_opt="--directory" + shift 1 + while getopts "r" flag; do + if [ x"$1" = x'-r' ]; then + unset directory_opt + fi + shift 1 + done + # ...and parse for a potential parameter afterwards. As we shifted things out of $* in during getops, we need to look at $1 + VCSH_REPO_NAME=$1; export VCSH_REPO_NAME + + if [ -n "$VCSH_REPO_NAME" ]; then + list_untracked_helper $VCSH_REPO_NAME + else + for VCSH_REPO_NAME in $(list); do + list_untracked_helper $VCSH_REPO_NAME + done + fi + cat $temp_file_untracked + + unset directory_opt directory_component + rm -f $temp_file_others $temp_file_untracked $temp_file_untracked_copy || fatal 'Could not delete temp files' +} + +list_untracked_helper() { + export GIT_DIR="$VCSH_REPO_D/$VCSH_REPO_NAME.git" + git ls-files --others "$directory_opt" | ( + while read line; do + echo "$line" + directory_component=${line%%/*} + [ -d "$directory_component" ] && printf '%s/\n' "$directory_component" + done + ) | sort -u > $temp_file_others + if [ -z "$ran_once" ]; then + ran_once=1 + cp $temp_file_others $temp_file_untracked || fatal 'Could not copy temp file' + fi + cp $temp_file_untracked $temp_file_untracked_copy || fatal 'Could not copy temp file' + comm -12 --nocheck-order $temp_file_others $temp_file_untracked_copy > $temp_file_untracked } pull() { @@ -307,22 +366,29 @@ run() { status() { if [ -n "$VCSH_REPO_NAME" ]; then - GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR - use - git status --short --untracked-files='no' - VCSH_COMMAND_RETURN_CODE=$? + status_helper $VCSH_REPO_NAME else for VCSH_REPO_NAME in $(list); do echo "$VCSH_REPO_NAME:" - GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR - use - git status --short --untracked-files='no' - VCSH_COMMAND_RETURN_CODE=$? + status_helper $VCSH_REPO_NAME echo done fi } +status_helper() { + GIT_DIR=$VCSH_REPO_D/$VCSH_REPO_NAME.git; export GIT_DIR + use + remote_tracking_branch=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2> /dev/null) && { + commits_behind=$(git log ..${remote_tracking_branch} --oneline | wc -l) + commits_ahead=$(git log ${remote_tracking_branch}.. --oneline | wc -l) + [ ${commits_behind} -ne 0 ] && echo "Behind $remote_tracking_branch by $commits_behind commits" + [ ${commits_ahead} -ne 0 ] && echo "Ahead of $remote_tracking_branch by $commits_ahead commits" + } + git status --short --untracked-files='no' + VCSH_COMMAND_RETURN_CODE=$? +} + upgrade() { hook pre-upgrade # fake-bare repositories are not bare, actually. Set this to false @@ -351,6 +417,7 @@ use() { } which() { + [ -e "$VCSH_COMMAND_PARAMETER" ] || fatal "'$VCSH_COMMAND_PARAMETER' does not exist" 1 for VCSH_REPO_NAME in $(list); do for VCSH_FILE in $(get_files); do echo "$VCSH_FILE" | grep -q "$VCSH_COMMAND_PARAMETER" && echo "$VCSH_REPO_NAME: $VCSH_FILE" @@ -463,6 +530,7 @@ elif [ x"$VCSH_COMMAND" = x'delete' ] || elif [ x"$VCSH_COMMAND" = x'commit' ] || [ x"$VCSH_COMMAND" = x'list' ] || [ x"$VCSH_COMMAND" = x'list-tracked' ] || + [ x"$VCSH_COMMAND" = x'list-untracked' ] || [ x"$VCSH_COMMAND" = x'pull' ] || [ x"$VCSH_COMMAND" = x'push' ]; then : @@ -510,6 +578,17 @@ check_dir "$VCSH_REPO_D" verbose "$VCSH_COMMAND begin" VCSH_COMMAND=$(echo "$VCSH_COMMAND" | sed 's/-/_/g'); export VCSH_COMMAND + +# Source repo-specific configuration file +[ -r "$XDG_CONFIG_HOME/vcsh/config.d/$VCSH_REPO_NAME" ] && . "$XDG_CONFIG_HOME/vcsh/config.d/$VCSH_REPO_NAME" + +# source overlay functions +for overlay in "$VCSH_OVERLAY_D/$VCSH_COMMAND"* "$VCSH_OVERLAY_D/$VCSH_REPO_NAME.$VCSH_COMMAND"*; do + [ -r "$overlay" ] || continue + info "sourcing '$overlay'" + . "$overlay" +done + hook pre-command $VCSH_COMMAND "$@" hook post-command