[meta] Add homeshick
This commit is contained in:
parent
9886e23d88
commit
700ad3cca1
4
Makefile
4
Makefile
|
@ -44,3 +44,7 @@ update-kubectx:
|
|||
|
||||
update-plug.vim:
|
||||
curl -s -L https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim > $$(~/bin/chezmoi source-path ~/.vim/autoload/plug.vim)
|
||||
|
||||
update-homeschick:
|
||||
curl -s -L -o homeshick-master.tar.gz https://github.com/andsens/homeshick/archive/master.tar.gz
|
||||
chezmoi import --strip-components 1 --destination ${HOME}/.homesick/repos/homeshick homeshick-master.tar.gz
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
# Contribution guidelines #
|
||||
|
||||
## Reporting issues ##
|
||||
Make sure that what you are experiencing is actually an error and that it lies
|
||||
with homeshick (often it can be a git configuration error)
|
||||
|
||||
### Questions ###
|
||||
If you have a question be sure to read
|
||||
[the documentation](https://github.com/andsens/homeshick/wiki) first.
|
||||
Often you will find the answer to it there.
|
||||
|
||||
### Description ###
|
||||
As with bug reports everywhere else:
|
||||
|
||||
* state the action(s) you took
|
||||
* explain what outcome you expected
|
||||
* describe the actual result
|
||||
|
||||
You will also need to report which operating system you encountered the issue on
|
||||
and which shell you used (type `echo $SHELL` in your terminal if you are unsure).
|
||||
|
||||
### Reproducing ###
|
||||
Unless you ran in to a [heisenbug](http://en.wikipedia.org/wiki/Heisenbug),
|
||||
it should be possible to reproduce the
|
||||
bug in a testing environment. To that end run
|
||||
`$HOME/.homesick/repos/homeshick/test/interactive` and reproduce the bug. This
|
||||
script drops you into a new shell where `$HOME` is set to an (almost) empty
|
||||
temporary folder. If you cannot reproduce the bug there, the error is likely
|
||||
with your setup and not homeshick. Otherwise attach the commands you executed
|
||||
in that environment to the issue.
|
||||
|
||||
## Pull requests ##
|
||||
|
||||
### Branching
|
||||
**Work from and create pull requests on `development`, not `master`.**
|
||||
|
||||
`master` always represents the latest release since that is the way homeshick
|
||||
updates itself. The `development` branch is where work is done for the next
|
||||
release version of homeshick.
|
||||
|
||||
### Code style ###
|
||||
* Indent with tabs and align with spaces.
|
||||
* Always use double brackets for `if` blocks
|
||||
* Run your changes through
|
||||
[shellcheck](https://www.shellcheck.net/) with `test/shellcheck`
|
||||
and the [bats](https://github.com/sstephenson/bats) testsuite with `test/run`
|
||||
|
||||
Use the supplied [editorconfig](http://editorconfig.org) file.
|
||||
Most editors have [editorconfig plugins](http://editorconfig.org/#download)
|
||||
to apply these settings.
|
||||
|
||||
### Content ###
|
||||
**Every PR should only contain one feature change, bug fix or typo correction.**
|
||||
|
||||
Commits should be atomic units of work, if they are not you should rebase them
|
||||
so that they are (typo corrections from a previous change for example do not
|
||||
justify a commit).
|
||||
|
||||
### Description ###
|
||||
The PR should clearly describe what problem the change fixes.
|
||||
A feature addition with no justification and use-case will be rejected.
|
||||
|
||||
### Testing ###
|
||||
Unless the code-change is a refactor, you should always add unit tests. When
|
||||
fixing a bug there should be a new test case that fails with the old code and
|
||||
succeeds with the new code. When introducing a new feature, it should be
|
||||
tested extensively, a single test case will not suffice.
|
||||
|
||||
Note that bats does not fail a test case when using double brackets.
|
||||
To assert variable values and file existance you *must* use single brackets!
|
||||
|
||||
Also consider negative test cases (e.g. what happens when a non-existing
|
||||
castlename is passed as an argument?).
|
||||
|
||||
You can read about the details of the testing framework in the
|
||||
[testing documentation](https://github.com/andsens/homeshick/wiki/Testing).
|
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2012-2014 Anders Ingemann
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,43 @@
|
|||
homeshick [![Build Status](https://travis-ci.org/andsens/homeshick.png?branch=development)](https://travis-ci.org/andsens/homeshick)
|
||||
=========
|
||||
In Unix, configuration files are king.
|
||||
Tailoring tools to suit your needs through configuration can be empowering.
|
||||
An immense number of hours is spent on getting these adjustments just right,
|
||||
but once you leave the confines of your own computer, these local optimizations are left behind.
|
||||
|
||||
By the power of git, homeshick enables you to bring the symphony of settings
|
||||
you have poured your heart into with you to remote computers.
|
||||
With it you can begin to focus even more energy on bettering your work environment
|
||||
since the benefits are reaped on whichever machine you are using.
|
||||
|
||||
However bare bones these machines are, provided that at least Bash 3 and Git 1.5 are available you can use homeshick.
|
||||
homeshick can handle multiple dotfile repositories. This means that you can install
|
||||
larger frameworks like [oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh)
|
||||
or a multitude of emacs or vim plugins alongside your own customizations without clutter.
|
||||
|
||||
For detailed [installation instructions](https://github.com/andsens/homeshick/wiki/Installation), [tutorials](https://github.com/andsens/homeshick/wiki/Tutorials) and [tips](https://github.com/andsens/homeshick/wiki/Automatic-deployment) & [tricks](https://github.com/andsens/homeshick/wiki/Symlinking) have a look at the [wiki](https://github.com/andsens/homeshick/wiki).
|
||||
|
||||
Quick install
|
||||
-------------
|
||||
|
||||
homeshick is installed to your own home directory and does not require root privileges to be installed.
|
||||
```sh
|
||||
git clone https://github.com/andsens/homeshick.git $HOME/.homesick/repos/homeshick
|
||||
```
|
||||
*Note: If you'd like to help testing new features before they are released use `git clone --branch testing https://...`*
|
||||
|
||||
To invoke homeshick, source the `homeshick.sh` script from your rc-script:
|
||||
```sh
|
||||
# from sh and its derivates (bash, dash, ksh, zsh etc.)
|
||||
printf '\nsource "$HOME/.homesick/repos/homeshick/homeshick.sh"' >> $HOME/.bashrc
|
||||
# csh and derivatives (i.e. tcsh)
|
||||
printf '\nalias homeshick source "$HOME/.homesick/repos/homeshick/homeshick.csh"\n' >> $HOME/.cshrc
|
||||
# fish shell
|
||||
echo \n'source "$HOME/.homesick/repos/homeshick/homeshick.fish"' >> "$HOME/.config/fish/config.fish"
|
||||
```
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
Before submitting pull requests or reporting bugs, please make sure to read
|
||||
the [contribution guidelines](CONTRIBUTING.md).
|
|
@ -0,0 +1,212 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
repos="$HOME/.homesick/repos"
|
||||
# Include all helper functions. We will include the required command function later on.
|
||||
homeshick=${HOMESHICK_DIR:-$HOME/.homesick/repos/homeshick}
|
||||
# On travis-ci exit_status for some reason errors out, ignore it
|
||||
# shellcheck disable=SC1090
|
||||
source "$homeshick/lib/exit_status.sh"
|
||||
# shellcheck source=lib/fs.sh
|
||||
source "$homeshick/lib/fs.sh"
|
||||
# shellcheck source=lib/git.sh
|
||||
source "$homeshick/lib/git.sh"
|
||||
# shellcheck source=lib/log.sh
|
||||
source "$homeshick/lib/log.sh"
|
||||
# shellcheck source=lib/prompt.sh
|
||||
source "$homeshick/lib/prompt.sh"
|
||||
|
||||
# lots of global variables in here, so just disable SC2034 for the entire file
|
||||
# shellcheck disable=SC2034
|
||||
true
|
||||
|
||||
exit_status=$EX_SUCCESS
|
||||
|
||||
type git &>/dev/null || err "$EX_SOFTWARE" "git not found in path"
|
||||
|
||||
if [[ ! -d $repos ]]; then
|
||||
mkdir -p "$repos"
|
||||
fi
|
||||
|
||||
# used in pull.sh
|
||||
# shellcheck disable=SC2034
|
||||
T_START=$(date +%s)
|
||||
if [[ -z $GIT_VERSION ]]; then
|
||||
read -r _ _ GIT_VERSION _ < <(command git --version)
|
||||
if [[ ! $GIT_VERSION =~ ([0-9]+)(\.[0-9]+){0,3} ]]; then
|
||||
err "$EX_SOFTWARE" "could not determine git version"
|
||||
fi
|
||||
fi
|
||||
|
||||
TALK=true
|
||||
SKIP=false
|
||||
FORCE=false
|
||||
BATCH=false
|
||||
VERBOSE=false
|
||||
|
||||
# Retrieve all the flags preceeding a subcommand
|
||||
while [[ $# -gt 0 ]]; do
|
||||
if [[ $1 =~ ^- ]]; then
|
||||
# Convert combined short options into multiples short options (e.g. `-qb' to `-q -b')
|
||||
if [[ $1 =~ ^-[a-z]{2,} ]]; then
|
||||
param=$1
|
||||
shift
|
||||
set -- "${param:0:2}" "-${param:2}" "$@"
|
||||
unset param
|
||||
fi
|
||||
case $1 in
|
||||
-h | --help) cmd="help" ; shift; continue ;;
|
||||
-q | --quiet) TALK=false ; shift; continue ;;
|
||||
-s | --skip) SKIP=true ; shift; continue ;;
|
||||
-f | --force) FORCE=true ; shift; continue ;;
|
||||
-b | --batch) BATCH=true ; shift; continue ;;
|
||||
-v | --verbose) VERBOSE=true ; shift; continue ;;
|
||||
*) err "$EX_USAGE" "Unknown option '$1'" ;;
|
||||
esac
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
[[ $# -gt 0 ]] || cmd="help"
|
||||
|
||||
# Get the subcommand
|
||||
valid_commands=(cd clone generate list check updates refresh pull symlink link track help)
|
||||
if [[ ! $cmd ]]; then
|
||||
# We actually want literal matching of the rhs here, $1 shouldn't be a regexp
|
||||
# shellcheck disable=SC2076
|
||||
if [[ " ${valid_commands[*]} " =~ " $1 " ]]; then
|
||||
cmd=$1
|
||||
shift
|
||||
fi
|
||||
if [[ ! $cmd ]]; then
|
||||
err "$EX_USAGE" "Unknown command '$1'"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Get the arguments for the subcommand, also parse flags if there are any left
|
||||
while [[ $# -gt 0 ]]; do
|
||||
if [[ $1 =~ ^- ]]; then
|
||||
# Convert combined short options into multiples short options (e.g. `-qb' to `-q -b')
|
||||
if [[ $1 =~ ^-[a-z]{2,} ]]; then
|
||||
param=$1
|
||||
shift
|
||||
set -- "${param:0:2}" "-${param:2}" "$@"
|
||||
unset param
|
||||
fi
|
||||
case $1 in
|
||||
-h | --help)
|
||||
cmd="help"
|
||||
shift; continue ;;
|
||||
-q | --quiet)
|
||||
TALK=false
|
||||
shift; continue ;;
|
||||
-s | --skip)
|
||||
SKIP=true
|
||||
shift; continue ;;
|
||||
-f | --force)
|
||||
FORCE=true
|
||||
shift; continue ;;
|
||||
-b | --batch)
|
||||
BATCH=true
|
||||
shift; continue ;;
|
||||
-v | --verbose)
|
||||
VERBOSE=true
|
||||
shift; continue ;;
|
||||
*) err "$EX_USAGE" "Unknown option '$1'" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
case $cmd in
|
||||
cd | clone | generate | check | updates | pull | symlink | link)
|
||||
params+=("$1")
|
||||
shift; continue ;;
|
||||
refresh)
|
||||
[[ ! $threshhold ]] && threshhold=$(($1*86400)) || params+=("$1")
|
||||
shift; continue ;;
|
||||
track)
|
||||
[[ ! $castle ]] && castle=$1 || params+=("$1")
|
||||
shift; continue ;;
|
||||
list) err "$EX_USAGE" "The 'list' command does not take any arguments" ;;
|
||||
help)
|
||||
[[ ! $help_cmd ]] && help_cmd=$1
|
||||
shift; continue;;
|
||||
*) err "$EX_USAGE" "Unknown command '$1'" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# If no additional arguments are given, run the subcommand for every castle
|
||||
if [[ ${#params[@]} -eq 0 ]]; then
|
||||
case $cmd in
|
||||
check | updates | refresh | pull | symlink | link)
|
||||
while IFS= read -d $'\n' -r name ; do
|
||||
params+=("$name")
|
||||
done < <(list_castle_names) ;;
|
||||
# These commands require parameters, show the help message instead
|
||||
cd | clone | generate | track) help_cmd=$cmd; cmd="help"; exit_status=$EX_USAGE ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Default param for refresh is 7
|
||||
[[ ! $threshhold ]] && threshhold=$((7*86400))
|
||||
|
||||
# Include the file that implements the invoked command
|
||||
case $cmd in
|
||||
cd) ;;
|
||||
symlink)
|
||||
# shellcheck source=lib/commands/link.sh
|
||||
source "$homeshick/lib/commands/link.sh" ;;
|
||||
updates)
|
||||
# shellcheck source=lib/commands/check.sh
|
||||
source "$homeshick/lib/commands/check.sh" ;;
|
||||
*)
|
||||
# Don't know what will be included, so just disable the rule
|
||||
# shellcheck disable=SC1090
|
||||
source "$homeshick/lib/commands/$cmd.sh" ;;
|
||||
esac
|
||||
|
||||
case $cmd in
|
||||
list) list ;;
|
||||
cd) help cd ;; # cd is implemented in the homeshick.{sh,csh} helper script.
|
||||
help) help $help_cmd ;;
|
||||
*)
|
||||
for param in "${params[@]}"; do
|
||||
case $cmd in
|
||||
clone)
|
||||
clone "$param" ;;
|
||||
generate)
|
||||
generate "$param" ;;
|
||||
check|updates)
|
||||
(check "$param") ;;
|
||||
refresh)
|
||||
(refresh $threshhold "$param") ;;
|
||||
pull)
|
||||
(pull "$param") ;;
|
||||
symlink|link)
|
||||
symlink "$param" ;;
|
||||
track)
|
||||
track "$castle" "$param" ;;
|
||||
esac
|
||||
result=$?
|
||||
if [[ $result == "$EX_USAGE" ]]; then
|
||||
exit "$EX_USAGE"
|
||||
fi
|
||||
if [[ $exit_status == 0 && $result != 0 ]]; then
|
||||
exit_status=$result
|
||||
fi
|
||||
done
|
||||
case $cmd in
|
||||
clone)
|
||||
symlink_cloned_files "${params[@]}" ;;
|
||||
refresh)
|
||||
pull_outdated $threshhold "${params[@]}" ;;
|
||||
pull)
|
||||
symlink_new_files "${params[@]}" ;;
|
||||
esac
|
||||
result=$?
|
||||
if [[ $exit_status == 0 && $result != 0 ]]; then
|
||||
exit_status=$result
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
exit $exit_status
|
|
@ -0,0 +1,72 @@
|
|||
#compdef homeshick
|
||||
|
||||
_homeshick () {
|
||||
local context state line curcontext="$curcontext" ret=1
|
||||
_arguments -n : \
|
||||
{-q,--quiet}'[Suppress status output]' \
|
||||
'(-s --skip)'{-f,--force}'[Overwrite files that already exist]' \
|
||||
'(-f --force)'{-s,--skip}'[Skip files that already exist]' \
|
||||
{-b,--batch}'[Batch-mode: Skip interactive prompt]' \
|
||||
{-v,--verbose}'[Verbose-mode: Detailed status output]' \
|
||||
{-h,--help}'[Help message]' \
|
||||
'1:commands:(cd clone generate list check refresh pull link track help)' \
|
||||
'*::arguments:->arguments' \
|
||||
&& ret=0
|
||||
|
||||
case $state in
|
||||
(arguments)
|
||||
curcontext="${curcontext%:*:*}:homeshick-arguments-$words[1]:"
|
||||
case $words[1] in
|
||||
(cd)
|
||||
_arguments \
|
||||
'1:castles:_homeshick_repo_folders' \
|
||||
&& ret=0
|
||||
;;
|
||||
(check|refresh|pull)
|
||||
_arguments \
|
||||
'*: :_homeshick_castles' \
|
||||
&& ret=0
|
||||
;;
|
||||
(link)
|
||||
_arguments \
|
||||
'*: :_homeshick_castles' \
|
||||
&& ret=0
|
||||
;;
|
||||
(track)
|
||||
_arguments \
|
||||
'1: :_homeshick_castles' \
|
||||
"*: :_path_files" \
|
||||
&& ret=0
|
||||
;;
|
||||
(help)
|
||||
_arguments \
|
||||
':command:(cd clone generate list check refresh pull link track help)' \
|
||||
&& ret=0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
_homeshick_repo_folders() {
|
||||
_path_files -/W $HOME/.homesick/repos
|
||||
}
|
||||
|
||||
_homeshick_castles()
|
||||
{
|
||||
local castles repos="$HOME/.homesick/repos"
|
||||
castles=()
|
||||
for repo in $(find -L $repos -mindepth 2 -maxdepth 2 -type d -name .git); do
|
||||
castles+=($(_homeshick_basename "${repo%/.git}"))
|
||||
done
|
||||
_describe -t castles 'castle' castles && ret=0
|
||||
}
|
||||
|
||||
_homeshick_basename()
|
||||
{
|
||||
printf -- "${1##*/}"
|
||||
}
|
||||
|
||||
_homeshick "$@"
|
|
@ -0,0 +1,201 @@
|
|||
#!bash
|
||||
#
|
||||
###############################################################################
|
||||
#
|
||||
# Bash completion for homeshick (https://github.com/andsens/homeshick).
|
||||
#
|
||||
# To use, add this line (or equivalent) to your .bashrc:
|
||||
#
|
||||
# source ~/.homesick/repos/homeshick/completions/homeshick-completion.bash
|
||||
#
|
||||
###############################################################################
|
||||
#
|
||||
# Copyright (C) 2013 Jeremy Lin
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
_homeshick_basename()
|
||||
{
|
||||
echo "${1##*/}"
|
||||
}
|
||||
|
||||
_homeshick_castles()
|
||||
{
|
||||
local repos="$HOME/.homesick/repos"
|
||||
# This should be a while loop like in link.sh, leave it for now though
|
||||
# shellcheck disable=SC2044
|
||||
for repo in $(find -L "$repos" -mindepth 2 -maxdepth 2 -type d -name .git); do
|
||||
_homeshick_basename "${repo%/.git}"
|
||||
done
|
||||
}
|
||||
|
||||
_homeshick_complete_castles()
|
||||
{
|
||||
COMPREPLY=($(compgen -W "$(_homeshick_castles)" -- "$1"))
|
||||
}
|
||||
|
||||
_homeshick_complete()
|
||||
{
|
||||
# The comments at the bottom of the file explain what's going on here.
|
||||
if $_HOMESHICK_HAS_COMPOPT; then
|
||||
compopt +o default +o nospace
|
||||
COMPREPLY=()
|
||||
else
|
||||
COMPREPLY=('')
|
||||
fi
|
||||
|
||||
local -r cmds='
|
||||
cd
|
||||
clone
|
||||
generate
|
||||
list
|
||||
check
|
||||
refresh
|
||||
pull
|
||||
link
|
||||
track
|
||||
help
|
||||
symlink
|
||||
updates
|
||||
'
|
||||
local -r short_opts='-q -s -f -b -v'
|
||||
local -r long_opts='--quiet --skip --force --batch --verbose'
|
||||
local -r protocols='file ftp ftps git http https rsync ssh'
|
||||
|
||||
# Scan through the command line and find the homeshick command
|
||||
# (if present), as well as its expected position.
|
||||
local cmd
|
||||
local cmd_index=1 # Expected index of the command token.
|
||||
local i
|
||||
for (( i = 1; i < ${#COMP_WORDS[@]}; i++ )); do
|
||||
local word="${COMP_WORDS[i]}"
|
||||
case "$word" in
|
||||
-*)
|
||||
((cmd_index++))
|
||||
;;
|
||||
*)
|
||||
cmd="$word"
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
|
||||
if (( COMP_CWORD < cmd_index )); then
|
||||
# Offer option completions.
|
||||
case "$cur" in
|
||||
--*)
|
||||
COMPREPLY=($(compgen -W "$long_opts" -- "$cur"))
|
||||
;;
|
||||
-*)
|
||||
COMPREPLY=($(compgen -W "$short_opts" -- "$cur"))
|
||||
;;
|
||||
*)
|
||||
# Skip completion; we should never get here.
|
||||
;;
|
||||
esac
|
||||
elif (( COMP_CWORD == cmd_index )); then
|
||||
# Offer command name completions.
|
||||
COMPREPLY=($(compgen -W "$cmds" -- "$cur"))
|
||||
else
|
||||
# Offer command argument completions.
|
||||
case "$cmd" in
|
||||
check | pull | link | symlink | updates)
|
||||
# Offer one or more castle name completions.
|
||||
_homeshick_complete_castles "$cur"
|
||||
;;
|
||||
cd)
|
||||
# Offer exactly one castle name completion.
|
||||
if (( COMP_CWORD == cmd_index + 1 )); then
|
||||
_homeshick_complete_castles "$cur"
|
||||
fi
|
||||
;;
|
||||
refresh)
|
||||
# Offer a numerical completion for DAYS (mostly as a reminder
|
||||
# that this argument should be a number), then castle name
|
||||
# completions after that.
|
||||
if (( COMP_CWORD == cmd_index + 1 )); then
|
||||
COMPREPLY=({0..9})
|
||||
else
|
||||
_homeshick_complete_castles "$cur"
|
||||
fi
|
||||
;;
|
||||
track)
|
||||
# Offer one castle name completion, then filename completions
|
||||
# after that.
|
||||
if (( COMP_CWORD == cmd_index + 1 )); then
|
||||
_homeshick_complete_castles "$cur"
|
||||
else
|
||||
$_HOMESHICK_HAS_COMPOPT && compopt -o default
|
||||
# Let the default Readline filename completion take over.
|
||||
COMPREPLY=()
|
||||
fi
|
||||
;;
|
||||
clone)
|
||||
# Offer an initial protocol completion.
|
||||
if (( COMP_CWORD == cmd_index + 1 )); then
|
||||
$_HOMESHICK_HAS_COMPOPT && compopt -o nospace
|
||||
COMPREPLY=($(compgen -W "$protocols" -S '://' -- "$cur"))
|
||||
fi
|
||||
;;
|
||||
help)
|
||||
# Offer exactly one command name completion.
|
||||
if (( COMP_CWORD == cmd_index + 1 )); then
|
||||
COMPREPLY=($(compgen -W "$cmds" -- "$cur"))
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
# Unknown command or unknowable argument.
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
# The behavior of 'compgen -f' is pretty bizarre, in that if there's a
|
||||
# directory 'foo', then compgen simply completes 'foo' and moves on, rather
|
||||
# than offering to complete names of files underneath 'foo', which is what
|
||||
# Bash (or Readline, actually) usually does.
|
||||
#
|
||||
# You can get the usual Readline filename completion behavior by doing
|
||||
# 'complete -o default -F <func>', in which case the Readline filename
|
||||
# completer is called if COMPREPLY=(). But this also means that you can no
|
||||
# longer deny completion with COMPREPLY=() -- now Readline will always offer
|
||||
# to complete a filename, even if this is inappropriate (e.g., the command
|
||||
# takes no further arguments).
|
||||
#
|
||||
# In Bash 4.0 and above, there's a way to work around this problem. These
|
||||
# versions have a 'compopt' builtin, which allows '-o default' to be enabled
|
||||
# or disabled as needed. The workaround, then, is to disable '-o default'
|
||||
# at the top of the completion function, and enable it as needed later on.
|
||||
#
|
||||
# For older Bash releases, there's a different workaround that doesn't work
|
||||
# quite as well, but is still better than nothing. This workaround is to
|
||||
# always enable '-o default' (which can't be disabled later), and then to use
|
||||
# COMPREPLY=('') to semi-deny completion. That is, pressing Tab will just add
|
||||
# space characters to the command line, rather than generating filenames.
|
||||
#
|
||||
if type compopt &>/dev/null; then
|
||||
_HOMESHICK_HAS_COMPOPT=true
|
||||
else
|
||||
_HOMESHICK_HAS_COMPOPT=false
|
||||
fi
|
||||
complete -o default -F _homeshick_complete homeshick
|
|
@ -0,0 +1,83 @@
|
|||
function __fish_homeshick_strip_options
|
||||
set cmd (commandline -opc)
|
||||
if [ (count $cmd) -lt 2 ]
|
||||
return 1
|
||||
end
|
||||
for item in $cmd[2..-1]
|
||||
if [ (echo $item | sed 's/^\(.\).*/\1/') = "-" ]
|
||||
continue
|
||||
end
|
||||
echo $item
|
||||
end
|
||||
end
|
||||
|
||||
function __fish_homeshick_needs_command
|
||||
set cmd (__fish_homeshick_strip_options)
|
||||
if [ (count $cmd) -eq 0 ]
|
||||
return 0
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
function __fish_homeshick_using_command
|
||||
set cmd (__fish_homeshick_strip_options)
|
||||
if [ (count $cmd) -eq 0 ]
|
||||
return 1
|
||||
end
|
||||
if [ $argv[1] = $cmd[1] ]
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
function __fish_homeshick_list_castles
|
||||
set repos "$HOME/.homesick/repos"
|
||||
for repo in (find -L $repos -mindepth 2 -maxdepth 2 -type d -name .git -exec dirname \{\} \;)
|
||||
basename $repo
|
||||
end
|
||||
end
|
||||
|
||||
# general options
|
||||
complete -f -c homeshick -s q -l quiet -d 'Suppress status output'
|
||||
complete -f -c homeshick -s s -l skip -d 'Skip files that already exist'
|
||||
complete -f -c homeshick -s f -l force -d 'Overwrite files that already exist'
|
||||
complete -f -c homeshick -s b -l batch -d 'Batch-mode: Skip interactive prompts / Choose the default'
|
||||
complete -f -c homeshick -s v -l verbose -d 'Verbose-mode: Detailed status output'
|
||||
|
||||
# cd
|
||||
complete -f -c homeshick -n '__fish_homeshick_needs_command' -a cd -r -d 'Enter a castle'
|
||||
complete -f -c homeshick -n '__fish_homeshick_using_command cd' -a '(__fish_homeshick_list_castles)' -d 'Castle'
|
||||
|
||||
# clone
|
||||
complete -f -c homeshick -n '__fish_homeshick_needs_command' -a clone -r -d 'Clone URI as a castle for homeshick'
|
||||
|
||||
# generate
|
||||
complete -f -c homeshick -n '__fish_homeshick_needs_command' -a generate -r -d 'Generate a castle repo'
|
||||
|
||||
# list
|
||||
complete -f -c homeshick -n '__fish_homeshick_needs_command' -a list -d 'List cloned castles'
|
||||
|
||||
# check
|
||||
complete -f -c homeshick -n '__fish_homeshick_needs_command' -a check -d 'Check a castle for updates'
|
||||
complete -f -c homeshick -n '__fish_homeshick_using_command check' -a '(__fish_homeshick_list_castles)' -d 'Castle'
|
||||
|
||||
# refresh
|
||||
complete -f -c homeshick -n '__fish_homeshick_needs_command' -a refresh -d 'Check if a castle needs refreshing'
|
||||
complete -f -c homeshick -n '__fish_homeshick_using_command refresh' -a '(__fish_homeshick_list_castles)' -d 'Castle'
|
||||
|
||||
# pull
|
||||
complete -f -c homeshick -n '__fish_homeshick_needs_command' -a pull -d 'Update a castle'
|
||||
complete -f -c homeshick -n '__fish_homeshick_using_command pull' -a '(__fish_homeshick_list_castles)' -d 'Castle'
|
||||
|
||||
# link
|
||||
complete -f -c homeshick -n '__fish_homeshick_needs_command' -a link -d 'Symlink all dotfiles from a castle'
|
||||
complete -f -c homeshick -n '__fish_homeshick_using_command link' -a '(__fish_homeshick_list_castles)' -d 'Castle'
|
||||
|
||||
# track
|
||||
complete -f -c homeshick -n '__fish_homeshick_needs_command' -a track -d 'Add a file to a castle'
|
||||
complete -c homeshick -n '__fish_homeshick_using_command track' -a '(__fish_homeshick_list_castles)' -d 'Castle'
|
||||
|
||||
# help
|
||||
complete -f -c homeshick -n '__fish_homeshick_needs_command' -a help -d 'Show usage of task'
|
||||
complete -f -c homeshick -n '__fish_homeshick_using_command help' -a 'cd clone generate list check refresh pull link track' -d 'Task'
|
|
@ -0,0 +1,13 @@
|
|||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.sh]
|
||||
indent_style = tab
|
||||
|
||||
[*.md]
|
||||
max_line_length = 80
|
||||
|
|
@ -0,0 +1 @@
|
|||
*.sublime-*
|
|
@ -0,0 +1,29 @@
|
|||
language: bash
|
||||
|
||||
dist: bionic
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- shellcheck
|
||||
- expect
|
||||
- tcsh
|
||||
|
||||
before_script:
|
||||
- git clone https://github.com/sstephenson/bats.git /tmp/bats
|
||||
- mkdir -p /tmp/local
|
||||
- bash /tmp/bats/install.sh /tmp/local
|
||||
- export PATH=$PATH:/tmp/local/bin
|
||||
- sudo apt-add-repository ppa:fish-shell/release-2 --yes
|
||||
- sudo apt-get -qq update
|
||||
- sudo apt-get -qq install fish
|
||||
|
||||
script:
|
||||
- fish --version
|
||||
- csh --version
|
||||
- test/shellcheck
|
||||
- bats --tap test/suites
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
|
@ -0,0 +1,17 @@
|
|||
# This helper script should be sourced via an alias, e.g.
|
||||
#
|
||||
# alias homeshick "source $HOME/.homesick/repos/homeshick/homeshick.csh"
|
||||
#
|
||||
if ( "$1" == "cd" && "x$2" != "x" ) then
|
||||
if ( -d "$HOME/.homesick/repos/$2/home" ) then
|
||||
cd "$HOME/.homesick/repos/$2/home"
|
||||
else
|
||||
cd "$HOME/.homesick/repos/$2"
|
||||
endif
|
||||
else
|
||||
if ( $?HOMESHICK_DIR ) then
|
||||
$HOMESHICK_DIR/bin/homeshick $*
|
||||
else
|
||||
$HOME/.homesick/repos/homeshick/bin/homeshick $*
|
||||
endif
|
||||
endif
|
|
@ -0,0 +1,14 @@
|
|||
# This script should be sourced in the context of your shell like so:
|
||||
# source $HOME/.homesick/repos/homeshick/homeshick.fish
|
||||
# Once the homeshick() function is defined, you can type
|
||||
# "homeshick cd CASTLE" to enter a castle.
|
||||
|
||||
function homeshick
|
||||
if test \( (count $argv) = 2 -a "$argv[1]" = "cd" \)
|
||||
cd "$HOME/.homesick/repos/$argv[2]"
|
||||
else if set -q HOMESHICK_DIR
|
||||
eval $HOMESHICK_DIR/bin/homeshick (string escape -- $argv)
|
||||
else
|
||||
eval $HOME/.homesick/repos/homeshick/bin/homeshick (string escape -- $argv)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env sh
|
||||
# This script should be sourced in the context of your shell like so:
|
||||
# source $HOME/.homeshick/repos/.homeshick/homeshick.sh
|
||||
# Once the homeshick() function is defined, you can type
|
||||
# "homeshick cd CASTLE" to enter a castle.
|
||||
|
||||
homeshick () {
|
||||
if [ "$1" = "cd" ] && [ -n "$2" ]; then
|
||||
# We want replicate cd behavior, so don't use cd ... ||
|
||||
# shellcheck disable=SC2164
|
||||
cd "$HOME/.homesick/repos/$2"
|
||||
else
|
||||
"${HOMESHICK_DIR:-$HOME/.homesick/repos/homeshick}/bin/homeshick" "$@"
|
||||
fi
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
#!/bin/bash
|
||||
|
||||
function check {
|
||||
local exit_status=$EX_SUCCESS
|
||||
[[ ! $1 ]] && help_err check
|
||||
local castle=$1
|
||||
# repos is a global variable
|
||||
# shellcheck disable=SC2154
|
||||
local repo="$repos/$castle"
|
||||
pending 'checking' "$castle"
|
||||
castle_exists 'check' "$castle"
|
||||
|
||||
local ref
|
||||
local branch
|
||||
# Fetch the current branch name
|
||||
ref=$(cd "$repo" && git symbolic-ref HEAD 2>/dev/null)
|
||||
branch=${ref#refs/heads/}
|
||||
# Get the upstream remote of that branch
|
||||
local remote_name
|
||||
local remote_url
|
||||
remote_name=$(cd "$repo" && git config "branch.$branch.remote" 2>/dev/null)
|
||||
remote_url=$(cd "$repo" && git config "remote.$remote_name.url" 2>/dev/null)
|
||||
# Get the HEAD of the current branch on the upstream remote
|
||||
local remote_head
|
||||
remote_head=$(git ls-remote --heads "$remote_url" "$branch" 2>/dev/null | cut -f 1)
|
||||
if [[ $remote_head ]]; then
|
||||
local local_head
|
||||
local_head=$(cd "$repo" && git rev-parse HEAD)
|
||||
if [[ $remote_head == "$local_head" ]]; then
|
||||
local git_status
|
||||
git_status=$(cd "$repo" && git status --porcelain 2>/dev/null)
|
||||
if [[ -z $git_status ]]; then
|
||||
success 'up to date'
|
||||
exit_status=$EX_SUCCESS
|
||||
else
|
||||
fail 'modified'
|
||||
exit_status=$EX_MODIFIED
|
||||
fi
|
||||
else
|
||||
local merge_base
|
||||
local checked_ref
|
||||
merge_base=$(cd "$repo" && git merge-base "$remote_head" "$local_head" 2>/dev/null)
|
||||
checked_ref=$(cd "$repo" && git rev-parse --verify "$remote_head" 2>/dev/null)
|
||||
# inlining checked_ref result makes the code unreadable
|
||||
# shellcheck disable=SC2181
|
||||
if [[ $? == 0 && $merge_base != "" && $merge_base == "$checked_ref" ]]; then
|
||||
fail 'ahead'
|
||||
exit_status=$EX_AHEAD
|
||||
else
|
||||
fail 'behind'
|
||||
exit_status=$EX_BEHIND
|
||||
fi
|
||||
fi
|
||||
else
|
||||
ignore 'uncheckable'
|
||||
exit_status=$EX_UNAVAILABLE
|
||||
fi
|
||||
return "$exit_status"
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
#!/bin/bash
|
||||
|
||||
function clone {
|
||||
[[ ! $1 ]] && help_err clone
|
||||
local git_repo=$1
|
||||
is_github_shorthand "$git_repo"
|
||||
if is_github_shorthand "$git_repo"; then
|
||||
if [[ -e "$git_repo/.git" ]]; then
|
||||
local msg="$git_repo also exists as a filesystem path,"
|
||||
msg="${msg} use \`homeshick clone ./$git_repo' to circumvent the github shorthand"
|
||||
warn 'clone' "$msg"
|
||||
fi
|
||||
git_repo="https://github.com/$git_repo.git"
|
||||
fi
|
||||
local repo_path
|
||||
# repos is a global variable
|
||||
# shellcheck disable=SC2154
|
||||
repo_path=$repos"/"$(repo_basename "$git_repo")
|
||||
pending 'clone' "$git_repo"
|
||||
test -e "$repo_path" && err "$EX_ERR" "$repo_path already exists"
|
||||
|
||||
local git_out
|
||||
version_compare "$GIT_VERSION" 1.6.5
|
||||
if [[ $? != 2 ]]; then
|
||||
git_out=$(git clone --recursive "$git_repo" "$repo_path" 2>&1) || \
|
||||
err "$EX_SOFTWARE" "Unable to clone $git_repo. Git says:" "$git_out"
|
||||
success
|
||||
else
|
||||
git_out=$(git clone "$git_repo" "$repo_path" 2>&1) || \
|
||||
err "$EX_SOFTWARE" "Unable to clone $git_repo. Git says:" "$git_out"
|
||||
success
|
||||
|
||||
pending 'submodules' "$git_repo"
|
||||
git_out=$(cd "$repo_path"; git submodule update --init 2>&1) || \
|
||||
err "$EX_SOFTWARE" "Unable to clone submodules for $git_repo. Git says:" "$git_out"
|
||||
success
|
||||
fi
|
||||
return "$EX_SUCCESS"
|
||||
}
|
||||
|
||||
function symlink_cloned_files {
|
||||
local cloned_castles=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
local git_repo=$1
|
||||
if is_github_shorthand "$git_repo"; then
|
||||
git_repo="https://github.com/$git_repo.git"
|
||||
fi
|
||||
local castle
|
||||
castle=$(repo_basename "$git_repo")
|
||||
shift
|
||||
local repo="$repos/$castle"
|
||||
if [[ ! -d $repo/home ]]; then
|
||||
continue;
|
||||
fi
|
||||
local num_files
|
||||
num_files=$(find "$repo/home" -mindepth 1 -maxdepth 1 | wc -l | tr -dc "0123456789")
|
||||
if [[ $num_files -gt 0 ]]; then
|
||||
cloned_castles+=("$castle")
|
||||
fi
|
||||
done
|
||||
ask_symlink "${cloned_castles[@]}"
|
||||
return "$EX_SUCCESS"
|
||||
}
|
||||
|
||||
# Convert username/repo into https://github.com/username/repo.git
|
||||
function is_github_shorthand {
|
||||
if [[ ! $1 =~ \.git$ && $1 =~ ^([0-9A-Za-z-]+/[0-9A-Za-z_\.-]+)$ ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Get the repo name from an URL
|
||||
function repo_basename {
|
||||
if [[ $1 =~ ^[^/:]+: ]]; then
|
||||
# For scp-style syntax like '[user@]host.xz:path/to/repo.git/',
|
||||
# remove the '[user@]host.xz:' part.
|
||||
basename "${1#*:}" .git
|
||||
else
|
||||
basename "$1" .git
|
||||
fi
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#!/bin/bash
|
||||
|
||||
function generate {
|
||||
[[ ! $1 ]] && help_err generate
|
||||
local castle=$1
|
||||
# repos is a global variable
|
||||
# shellcheck disable=SC2154
|
||||
local repo="$repos/$castle"
|
||||
pending 'generate' "$castle"
|
||||
if [[ -d $repo ]]; then
|
||||
err "$EX_ERR" "The castle $castle already exists"
|
||||
fi
|
||||
|
||||
mkdir "$repo"
|
||||
local git_out
|
||||
git_out=$(cd "$repo" && git init 2>&1) || \
|
||||
err "$EX_SOFTWARE" "Unable to initialize repository $repo. Git says:" "$git_out"
|
||||
mkdir "$repo/home"
|
||||
success
|
||||
return "$EX_SUCCESS"
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
#!/bin/bash
|
||||
|
||||
# help is used globally
|
||||
# shellcheck disable=SC2120
|
||||
function help {
|
||||
if [[ $1 ]]; then
|
||||
extended_help "$1"
|
||||
exit "$EX_SUCCESS"
|
||||
fi
|
||||
|
||||
printf "homes\e[1;34mh\e[0mick uses git in concert with symlinks to track your precious dotfiles.
|
||||
|
||||
Usage: homeshick [options] TASK
|
||||
|
||||
Tasks:
|
||||
homeshick cd CASTLE # Enter a castle
|
||||
homeshick clone URI.. # Clone URI as a castle for homeshick
|
||||
homeshick generate CASTLE.. # Generate a castle repo
|
||||
homeshick list # List cloned castles
|
||||
homeshick check [CASTLE..] # Check a castle for updates
|
||||
homeshick refresh [DAYS [CASTLE..]] # Check if a castle needs refreshing
|
||||
homeshick pull [CASTLE..] # Update a castle
|
||||
homeshick link [CASTLE..] # Symlinks all dotfiles from a castle
|
||||
homeshick track CASTLE FILE.. # Add a file to a castle
|
||||
homeshick help [TASK] # Show usage of a task
|
||||
|
||||
Aliases:
|
||||
symlink # Alias to link
|
||||
updates # Alias to check
|
||||
|
||||
Runtime options:
|
||||
-q, [--quiet] # Suppress status output
|
||||
-s, [--skip] # Skip files that already exist
|
||||
-f, [--force] # Overwrite files that already exist
|
||||
-b, [--batch] # Batch-mode: Skip interactive prompts / Choose the default
|
||||
-v, [--verbose] # Verbose-mode: Detailed status output
|
||||
|
||||
Note:
|
||||
To check, refresh, pull or symlink all your castles
|
||||
simply omit the CASTLE argument
|
||||
|
||||
"
|
||||
}
|
||||
|
||||
function extended_help {
|
||||
case $1 in
|
||||
cd)
|
||||
printf "Enters a castle's home directory.\n"
|
||||
printf "NOTE: For this to work, homeshick must be invoked via homeshick.{sh,csh,fish}.\n\n"
|
||||
printf "Usage:\n homeshick cd CASTLE"
|
||||
;;
|
||||
clone)
|
||||
printf "Clones URI as a castle for homeshick\n"
|
||||
printf "Usage:\n homeshick clone URL.."
|
||||
;;
|
||||
generate)
|
||||
printf "Generates a repo prepped for usage with homeshick\n"
|
||||
printf "Usage:\n homeshick generate CASTLE.."
|
||||
;;
|
||||
list)
|
||||
printf "Lists cloned castles\n"
|
||||
printf "Usage:\n homeshick list"
|
||||
;;
|
||||
check|updates)
|
||||
printf "Checks if a castle has been updated on the remote\n"
|
||||
printf "Usage:\n homeshick %s [CASTLE..]" "$1"
|
||||
;;
|
||||
refresh)
|
||||
printf "Checks if a castle has not been pulled in DAYS days.\n"
|
||||
printf "The default is one week.\n"
|
||||
printf "Usage:\n homeshick refresh [DAYS] [CASTLE..]"
|
||||
;;
|
||||
pull)
|
||||
printf "Updates a castle. Also recurse into submodules.\n"
|
||||
printf "Usage:\n homeshick pull [CASTLE..]"
|
||||
;;
|
||||
link|symlink)
|
||||
printf "Symlinks all dotfiles from a castle\n"
|
||||
printf "Usage:\n homeshick %s [CASTLE..]" "$1"
|
||||
;;
|
||||
track)
|
||||
printf "Adds a file to a castle.\n"
|
||||
printf "This moves the file into the castle and creates a symlink in its place.\n"
|
||||
printf "Usage:\n homeshick track CASTLE FILE.."
|
||||
;;
|
||||
help)
|
||||
printf "Shows usage of a task\n"
|
||||
printf "Usage:\n homeshick help [TASK]"
|
||||
;;
|
||||
*)
|
||||
# no args for help
|
||||
# shellcheck disable=SC2119
|
||||
help
|
||||
;;
|
||||
esac
|
||||
printf "\n\n"
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
#!/bin/bash
|
||||
|
||||
function symlink {
|
||||
[[ ! $1 ]] && help symlink
|
||||
local castle=$1
|
||||
castle_exists 'link' "$castle"
|
||||
# repos is a global variable
|
||||
# shellcheck disable=SC2154
|
||||
local repo="$repos/$castle"
|
||||
if [[ ! -d $repo/home ]]; then
|
||||
ignore 'ignored' "$castle"
|
||||
return "$EX_SUCCESS"
|
||||
fi
|
||||
# Run through the repo files using process substitution.
|
||||
# The get_repo_files call is at the bottom of this loop.
|
||||
# We set the IFS to nothing and the separator for `read' to NUL so that we
|
||||
# don't separate files with newlines in their name into two iterations.
|
||||
# `read's stdin comes from a third unused file descriptor because we are
|
||||
# using the real stdin for prompting whether the user wants to
|
||||
# overwrite or skip on conflicts.
|
||||
while IFS= read -d $'\0' -r relpath <&3 ; do
|
||||
local repopath="$repo/home/$relpath"
|
||||
local homepath="$HOME/$relpath"
|
||||
local rel_repopath
|
||||
rel_repopath=$(create_rel_path "$(dirname "$homepath")/" "$repopath") || return $?
|
||||
|
||||
if [[ -e $homepath || -L $homepath ]]; then
|
||||
# $homepath exists (but may be a dead symlink)
|
||||
if [[ -L $homepath && $(readlink "$homepath") == "$rel_repopath" ]]; then
|
||||
# $homepath symlinks to $repopath.
|
||||
if $VERBOSE; then
|
||||
ignore 'identical' "$relpath"
|
||||
fi
|
||||
continue
|
||||
elif [[ $(readlink "$homepath") == "$repopath" ]]; then
|
||||
# $homepath is an absolute symlink to $repopath
|
||||
if [[ -d $repopath && ! -L $repopath ]]; then
|
||||
# $repopath is a directory, but $homepath is a symlink -> legacy handling.
|
||||
rm "$homepath"
|
||||
else
|
||||
# replace it with a relative symlink
|
||||
rm "$homepath"
|
||||
fi
|
||||
else
|
||||
# $homepath does not symlink to $repopath
|
||||
# check if we should delete $homepath
|
||||
if [[ -d $homepath && -d $repopath && ! -L $repopath ]]; then
|
||||
# $repopath is a real directory while
|
||||
# $homepath is a directory or a symlinked directory
|
||||
# we do not take any action regardless of which it is.
|
||||
if $VERBOSE; then
|
||||
ignore 'identical' "$relpath"
|
||||
fi
|
||||
continue
|
||||
elif $SKIP; then
|
||||
ignore 'exists' "$relpath"
|
||||
continue
|
||||
elif ! $FORCE; then
|
||||
prompt_no 'conflict' "$relpath exists" "overwrite?" || continue
|
||||
fi
|
||||
# Delete $homepath.
|
||||
rm -rf "$homepath"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -d $repopath || -L $repopath ]]; then
|
||||
# $repopath is not a real directory so we create a symlink to it
|
||||
pending 'symlink' "$relpath"
|
||||
ln -s "$rel_repopath" "$homepath"
|
||||
else
|
||||
pending 'directory' "$relpath"
|
||||
mkdir "$homepath"
|
||||
fi
|
||||
|
||||
success
|
||||
# Fetch the repo files and redirect the output into file descriptor 3
|
||||
done 3< <(get_repo_files "$repo")
|
||||
return "$EX_SUCCESS"
|
||||
}
|
||||
|
||||
# Fetches all files and folders in a repository that are tracked by git
|
||||
# Works recursively on submodules as well
|
||||
# Disable SC2154, we cannot do it inline where $homeshick is used.
|
||||
# shellcheck disable=SC2154
|
||||
function get_repo_files {
|
||||
# Resolve symbolic links
|
||||
# e.g. on osx $TMPDIR is in /var/folders...
|
||||
# which is actually /private/var/folders...
|
||||
# We do this so that the root part of $toplevel can be replaced
|
||||
# git resolves symbolic links before it outputs $toplevel
|
||||
local root
|
||||
root=$(cd "$1" && pwd -P)
|
||||
(
|
||||
local path
|
||||
while IFS= read -d $'\n' -r path; do
|
||||
# Remove quotes from ls-files
|
||||
# (used when there are newlines in the path)
|
||||
path=${path/#\"/}
|
||||
path=${path/%\"/}
|
||||
# Check if home/ is a submodule
|
||||
[[ $path == 'home' ]] && continue
|
||||
# Remove the home/ part
|
||||
path=${path/#home\//}
|
||||
# Print the file path (NUL separated because \n can be used in filenames)
|
||||
# Disable SC2059, using %s messes with the filename
|
||||
# shellcheck disable=SC2059
|
||||
printf "$path\0"
|
||||
# Get the path of all the parent directories
|
||||
# up to the repo root.
|
||||
while true; do
|
||||
path=$(dirname "$path")
|
||||
# If path is '.' we're done
|
||||
[[ $path == '.' ]] && break
|
||||
# Print the path
|
||||
# shellcheck disable=SC2059
|
||||
printf "$path\0"
|
||||
done
|
||||
# Enter the repo, list the repo root files in home
|
||||
# and do the same for any submodules
|
||||
done < <(cd "$root" &&
|
||||
git ls-files 'home/' &&
|
||||
git submodule --quiet foreach --recursive \
|
||||
"$homeshick/lib/submodule_files.sh \"$root\" \"\$toplevel\" \"\$path\"")
|
||||
# Unfortunately we have to use an external script for `git submodule foreach'
|
||||
# because versions prior to ~ 2.0 use `eval' to execute the argument.
|
||||
# This somehow messes quite badly with string substitution.
|
||||
) | sort -zu # sort the results and make the list unique (-u), NUL is the line separator (-z)
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#!/bin/bash
|
||||
|
||||
function list {
|
||||
while IFS= read -d $'\n' -r reponame ; do
|
||||
local ref
|
||||
local branch
|
||||
# repos is a global variable
|
||||
# shellcheck disable=SC2154
|
||||
ref=$(cd "$repos/$reponame" && git symbolic-ref HEAD 2>/dev/null)
|
||||
branch=${ref#refs/heads/}
|
||||
local remote_name
|
||||
local remote_url
|
||||
remote_name=$(cd "$repos/$reponame" && git config "branch.$branch.remote" 2>/dev/null)
|
||||
remote_url=$(cd "$repos/$reponame" && git config "remote.$remote_name.url" 2>/dev/null)
|
||||
info "$reponame" "$remote_url"
|
||||
done < <(list_castle_names)
|
||||
return "$EX_SUCCESS"
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
#!/bin/bash
|
||||
|
||||
function pull {
|
||||
[[ ! $1 ]] && help_err pull
|
||||
local castle=$1
|
||||
# repos is a global variable
|
||||
# shellcheck disable=SC2154
|
||||
local repo="$repos/$castle"
|
||||
pending 'pull' "$castle"
|
||||
castle_exists 'pull' "$castle"
|
||||
if ! repo_has_upstream "$repo"; then
|
||||
ignore 'no upstream' "Could not pull $castle, it has no upstream"
|
||||
return "$EX_SUCCESS"
|
||||
fi
|
||||
|
||||
local git_out
|
||||
git_out=$(cd "$repo" && git pull 2>&1) || \
|
||||
err "$EX_SOFTWARE" "Unable to pull $repo. Git says:" "$git_out"
|
||||
|
||||
version_compare "$GIT_VERSION" 1.6.5
|
||||
if [[ $? != 2 ]]; then
|
||||
git_out=$(cd "$repo" && git submodule update --recursive --init 2>&1) || \
|
||||
err "$EX_SOFTWARE" "Unable update submodules for $repo. Git says:" "$git_out"
|
||||
else
|
||||
git_out=$(cd "$repo" && git submodule update --init 2>&1) || \
|
||||
err "$EX_SOFTWARE" "Unable update submodules for $repo. Git says:" "$git_out"
|
||||
fi
|
||||
success
|
||||
return "$EX_SUCCESS"
|
||||
}
|
||||
|
||||
function symlink_new_files {
|
||||
local updated_castles=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
local castle=$1
|
||||
shift
|
||||
local repo="$repos/$castle"
|
||||
if [[ ! -d $repo/home ]]; then
|
||||
continue;
|
||||
fi
|
||||
local git_out
|
||||
local now
|
||||
now=$(date +%s)
|
||||
if ! git_out=$(cd "$repo" && git diff --name-only --diff-filter=A "HEAD@{(($now-$T_START+1)).seconds.ago}" HEAD -- home 2>/dev/null | wc -l 2>&1); then
|
||||
continue # Ignore errors, this operation is not mission critical
|
||||
fi
|
||||
if [[ $git_out -gt 0 ]]; then
|
||||
updated_castles+=("$castle")
|
||||
fi
|
||||
done
|
||||
ask_symlink "${updated_castles[@]}"
|
||||
return "$EX_SUCCESS"
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
#!/bin/bash
|
||||
|
||||
function refresh {
|
||||
[[ ! $1 || ! $2 ]] && help_err refresh
|
||||
local threshhold=$1
|
||||
local castle=$2
|
||||
# repos is a global variable
|
||||
# shellcheck disable=SC2154
|
||||
local fetch_head="$repos/$castle/.git/FETCH_HEAD"
|
||||
pending 'checking' "$castle"
|
||||
castle_exists 'refresh' "$castle"
|
||||
|
||||
if [[ -e $fetch_head ]]; then
|
||||
local last_mod
|
||||
last_mod=$(stat -c %Y "$fetch_head" 2> /dev/null || stat -f %m "$fetch_head")
|
||||
if [[ $((T_START-last_mod)) -gt $threshhold ]]; then
|
||||
fail "outdated"
|
||||
return "$EX_TH_EXCEEDED"
|
||||
else
|
||||
success "fresh"
|
||||
return "$EX_SUCCESS"
|
||||
fi
|
||||
else
|
||||
fail "outdated"
|
||||
return "$EX_TH_EXCEEDED"
|
||||
fi
|
||||
}
|
||||
|
||||
function pull_outdated {
|
||||
local threshhold=$1; shift
|
||||
local outdated_castles=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
local castle=$1; shift
|
||||
local repo="$repos/$castle"
|
||||
if [[ ! -d $repo ]]; then
|
||||
# bogus argument, skip. User has already been warned by refresh()
|
||||
continue
|
||||
fi
|
||||
local fetch_head="$repo/.git/FETCH_HEAD"
|
||||
# When in interactive mode:
|
||||
# No matter if we are going to pull the castles or not
|
||||
# we reset the outdated ones by touching FETCH_HEAD
|
||||
if [[ -e $fetch_head ]]; then
|
||||
local last_mod
|
||||
last_mod=$(stat -c %Y "$fetch_head" 2> /dev/null || stat -f %m "$fetch_head")
|
||||
if [[ $((T_START-last_mod)) -gt $threshhold ]]; then
|
||||
outdated_castles+=("$castle")
|
||||
! $BATCH && touch "$fetch_head"
|
||||
fi
|
||||
else
|
||||
outdated_castles+=("$castle")
|
||||
! $BATCH && touch "$fetch_head"
|
||||
fi
|
||||
done
|
||||
ask_pull "${outdated_castles[@]}"
|
||||
return "$EX_SUCCESS"
|
||||
}
|
||||
|
||||
function ask_pull {
|
||||
if [[ $# -gt 0 ]]; then
|
||||
if [[ $# == 1 ]]; then
|
||||
msg="The castle $1 is outdated."
|
||||
else
|
||||
OIFS=$IFS
|
||||
IFS=,
|
||||
msg="The castles $* are outdated."
|
||||
IFS=$OIFS
|
||||
fi
|
||||
if prompt_no 'refresh' "$msg" 'pull?'; then
|
||||
# shellcheck source=lib/commands/pull.sh disable=SC2154
|
||||
source "$homeshick/lib/commands/pull.sh"
|
||||
for castle in "$@"; do
|
||||
pull "$castle"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
return "$EX_SUCCESS"
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
#!/bin/bash
|
||||
|
||||
function track {
|
||||
[[ ! $1 || ! $2 ]] && help track
|
||||
local castle=$1
|
||||
local filename
|
||||
filename=$(abs_path "$2")
|
||||
if [[ $filename != $HOME/* ]]; then
|
||||
err "$EX_ERR" "The file $filename must be in your home directory."
|
||||
fi
|
||||
# If the file is a dead symlink, we track it anyhow, hence the '! -L'
|
||||
if [[ ! -e $filename && ! -L $filename ]]; then
|
||||
err "$EX_ERR" "The file $filename does not exist."
|
||||
fi
|
||||
home_exists 'track' "$castle"
|
||||
|
||||
local files_to_track
|
||||
files_to_track=$(find "$filename" -name .git -prune -o -not -type d -print)
|
||||
if [[ -z $files_to_track ]]; then
|
||||
ignore 'track' 'No files to track'
|
||||
return "$EX_SUCCESS"
|
||||
fi
|
||||
|
||||
# check-ignore was only added in 1.8.2
|
||||
local check_ignore=false
|
||||
version_compare "$GIT_VERSION" 1.8.2
|
||||
[[ $? != 2 ]] && check_ignore=true
|
||||
|
||||
# repos is a global variable
|
||||
# shellcheck disable=SC2154
|
||||
local repo="$repos/$castle"
|
||||
oldIFS=$IFS
|
||||
IFS=$'\n'
|
||||
for homepath in $files_to_track; do
|
||||
IFS=$oldIFS
|
||||
|
||||
local relpath=${homepath#$HOME/}
|
||||
pending 'track' "$relpath"
|
||||
local relpath_in_repo="home/$relpath"
|
||||
local repopath="$repo/$relpath_in_repo"
|
||||
|
||||
if [[ -e $repopath ]]; then
|
||||
ignore 'exists' "The file $relpath is already being tracked."
|
||||
continue
|
||||
fi
|
||||
if $check_ignore; then
|
||||
if (cd "$repo" && git check-ignore --quiet "$relpath_in_repo") then
|
||||
ignore 'ignored' "The file $relpath would be ignored by git."
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -e $repopath && $FORCE = false ]]; then
|
||||
continue
|
||||
prompt_no 'conflict' "$repopath exists" "overwrite?" || continue
|
||||
fi
|
||||
local remote_folder
|
||||
remote_folder=$(dirname "$repopath")
|
||||
mkdir -p "$remote_folder"
|
||||
|
||||
# Check if the file is a relative symlink, if so we don't move it but create
|
||||
# an appropriate relative symlink instead that matches the new location
|
||||
if [[ -L $homepath ]]; then
|
||||
local target
|
||||
target=$(readlink "$homepath")
|
||||
if [[ $target =~ ^/ ]]; then
|
||||
# It's an absolute symlink, just move it
|
||||
mv -f "$homepath" "$repopath"
|
||||
else
|
||||
# Figure out the relative path from the symlink location in
|
||||
# the castle to the path the symlink points at
|
||||
|
||||
# Convert the relative target into an absolute one
|
||||
local abs_target
|
||||
local target_dir
|
||||
target_dir=$(abs_path "$(dirname "$homepath")")
|
||||
abs_target=$(clean_path "$target_dir/$target")
|
||||
# Get the relative path from the repopath dir to the target
|
||||
local rel_targetpath
|
||||
rel_targetpath=$(create_rel_path "$(dirname "$repopath")/" "$abs_target") || return $?
|
||||
ln -s "$rel_targetpath" "$repopath"
|
||||
# Remove $homepath so we can create the symlink further down
|
||||
rm "$homepath"
|
||||
fi
|
||||
else
|
||||
# Just a regular old file. Move it
|
||||
mv -f "$homepath" "$repopath"
|
||||
fi
|
||||
# Create the symlink in place of the moved file (simulate what the link command does)
|
||||
local rel_repopath
|
||||
rel_repopath=$(create_rel_path "$(dirname "$homepath")/" "$repopath") || return $?
|
||||
ln -s "$rel_repopath" "$homepath"
|
||||
|
||||
local git_out
|
||||
git_out=$(cd "$repo" && git add "$relpath_in_repo" 2>&1)
|
||||
status=$?
|
||||
if [[ $status == 128 && $check_ignore == false ]]; then
|
||||
# Currently our only option with git < 1.8.2, we can't be sure some other error hasn't occurred
|
||||
ignore 'ignored' "The file $relpath would be ignored by git."
|
||||
mv -f "$repopath" "$homepath"
|
||||
continue
|
||||
elif [[ $status != 0 ]]; then
|
||||
fail 'track' "Unable to add file to git. Git says: $git_out"
|
||||
exit
|
||||
fi
|
||||
success
|
||||
done
|
||||
return "$EX_SUCCESS"
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env bash
|
||||
# This script is meant to be used in conjunction with
|
||||
# `git submodule foreach'.
|
||||
# It runs outputs all files tracked by a submodule.
|
||||
# The paths are outputted relative to $root
|
||||
root="$1"
|
||||
toplevel="$2"
|
||||
path="$3"
|
||||
# toplevel/path relative to root
|
||||
repo=${toplevel/#$root/}/$path
|
||||
# If we are at root, remove the slash in front
|
||||
repo=${repo/#\//}
|
||||
# We are only interested in submodules under home/
|
||||
if [[ $repo =~ ^home ]]; then
|
||||
# just let cd fail if the path does not exist
|
||||
# shellcheck disable=2164
|
||||
cd "$toplevel/$path"
|
||||
# List the files and prefix every line
|
||||
# with the relative repo path
|
||||
git ls-files | sed "s#^#${repo//#/\\#}/#"
|
||||
fi
|
|
@ -0,0 +1,52 @@
|
|||
#!/bin/bash
|
||||
|
||||
|
||||
# constants file, disable SC2034
|
||||
# shellcheck disable=SC2034
|
||||
true
|
||||
|
||||
# List of semi-standard exit status codes
|
||||
|
||||
# Sources:
|
||||
# A: http://tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF
|
||||
# B: http://www.gnu.org/software/libc/manual/html_node/Exit-Status.html
|
||||
# C: sysexits.h
|
||||
EX_OK=0 # successful termination ## source: A
|
||||
EX_SUCCESS=0 # successful termination ## source: A
|
||||
EX_ERR=1 # Catchall for general errors ## source: A
|
||||
|
||||
# Don't use. Reserved for bash
|
||||
EX_SHELL=2 # Misuse of shell builtins ## source: A
|
||||
|
||||
EX_USAGE=64 # command line usage error ## source: C
|
||||
EX_DATAERR=65 # data format error ## source: C
|
||||
EX_NOINPUT=66 # cannot open input ## source: C
|
||||
EX_NOUSER=67 # addressee unknown ## source: C
|
||||
EX_NOHOST=68 # host name unknown ## source: C
|
||||
EX_UNAVAILABLE=69 # service unavailable ## source: C
|
||||
EX_SOFTWARE=70 # internal software error ## source: C
|
||||
EX_OSERR=71 # system error (e.g., can't fork) ## source: C
|
||||
EX_OSFILE=72 # critical OS file missing ## source: C
|
||||
EX_CANTCREAT=73 # can't create (user) output file ## source: C
|
||||
EX_IOERR=74 # input/output error ## source: C
|
||||
EX_TEMPFAIL=75 # temp failure; user is invited to retry ## source: C
|
||||
EX_PROTOCOL=76 # remote error in protocol ## source: C
|
||||
EX_NOPERM=77 # permission denied ## source: C
|
||||
EX_CONFIG=78 # configuration error ## source: C
|
||||
|
||||
# Don't use. Reserved for bash
|
||||
EX_NOEXEC=126 # Command invoked cannot execute ## source: A
|
||||
EX_NOTFOUND=127 # "command not found" ## source: A
|
||||
|
||||
# These two are in direct conflict, don't use
|
||||
EX_EXIT_ERR=128 # Invalid argument to exit ## source: A
|
||||
EX_EXEC_FAIL=128 # Failed to execute subprocess ## source: B
|
||||
|
||||
EX_SIGTERM=130 # Script terminated by Control-C ## source: A
|
||||
|
||||
|
||||
# Custom homeshick status codes (range: 79-113)
|
||||
EX_AHEAD=85 # local HEAD is ahead of its upstream branch
|
||||
EX_BEHIND=86 # local HEAD is behind its upstream branch
|
||||
EX_TH_EXCEEDED=87 # Time since last repository update is larger than the threshhold
|
||||
EX_MODIFIED=88 # local working directory has modified files
|
|
@ -0,0 +1,219 @@
|
|||
#!/bin/bash
|
||||
|
||||
function castle_exists {
|
||||
local action=$1
|
||||
local castle=$2
|
||||
# repos is a global variable, disable SC2154
|
||||
# shellcheck disable=SC2154
|
||||
local repo="$repos/$castle"
|
||||
if [[ ! -d $repo ]]; then
|
||||
err "$EX_ERR" "Could not $action $castle, expected $repo to exist"
|
||||
fi
|
||||
}
|
||||
|
||||
function home_exists {
|
||||
local action=$1
|
||||
local castle=$2
|
||||
local repo="$repos/$castle"
|
||||
if [[ ! -d $repo/home ]]; then
|
||||
err "$EX_ERR" "Could not $action $castle, expected $repo to contain a home folder"
|
||||
fi
|
||||
}
|
||||
|
||||
function list_castle_names {
|
||||
while IFS= read -d $'\0' -r repo ; do
|
||||
# Avoid using basename for a small speed-up
|
||||
# See link, for why it's OK to use bash string substitution in this case
|
||||
# https://github.com/andsens/homeshick/pull/181/files#r196206593
|
||||
local reponame
|
||||
reponame="${repo%/.git}"
|
||||
reponame="${reponame##*/}"
|
||||
printf "%s\n" "$reponame"
|
||||
done < <(find -L "$repos" -mindepth 2 -maxdepth 2 -name .git -type d -print0 | sort -z)
|
||||
return "$EX_SUCCESS"
|
||||
}
|
||||
|
||||
# Converts any path to an absolute path.
|
||||
# All path parts except the last one must exist.
|
||||
# A pwd option can be given as the first argument (like -P to resolve symlinks).
|
||||
# In order to resolve the last part of the path append "/." to it.
|
||||
function abs_path {
|
||||
local path
|
||||
local pwd_opt=""
|
||||
if [[ $# -eq 1 ]]; then
|
||||
path=$1
|
||||
else
|
||||
pwd_opt=$1
|
||||
path=$2
|
||||
fi
|
||||
local dir
|
||||
dir=$(dirname "$path")
|
||||
if [[ ! -e $dir ]]; then
|
||||
printf "The parent directory '%s' does not exist" "$dir" >&2
|
||||
return 1
|
||||
fi
|
||||
local real_dir
|
||||
local base
|
||||
real_dir=$(cd "$dir" >/dev/null && printf "%s" "$(pwd "$pwd_opt")") || return $?
|
||||
base=$(basename "$path")
|
||||
if [[ $base = "." ]]; then
|
||||
printf "%s\n" "$real_dir"
|
||||
elif [[ $base = "/" ]]; then
|
||||
printf "/\n"
|
||||
elif [[ $real_dir = "/" ]]; then
|
||||
printf "/%s\n" "$base"
|
||||
else
|
||||
printf "%s/%s\n" "$real_dir" "$base"
|
||||
fi
|
||||
}
|
||||
|
||||
# Removes unnecessary path parts, such as '/./' and 'somedir/../' and trailing slashes
|
||||
function clean_path {
|
||||
local path=$1
|
||||
|
||||
# Split path into parts
|
||||
local parts=()
|
||||
local rest=$path
|
||||
while [[ $rest != '.' && $rest != '/' ]]; do
|
||||
parts+=("$(basename "$rest")")
|
||||
rest=$(dirname "$rest")
|
||||
done
|
||||
# reverse $parts, it's a lot easier to follow the code below then
|
||||
local new_parts=()
|
||||
for (( idx=${#parts[@]}-1 ; idx>=0 ; idx-- )); do
|
||||
new_parts+=("${parts[$idx]}")
|
||||
done
|
||||
parts=("${new_parts[@]}")
|
||||
|
||||
local left
|
||||
local right
|
||||
local omit_left
|
||||
local omit_right
|
||||
# Run through the $parts until we cannot reduce it any longer
|
||||
while true; do
|
||||
omit_left=false
|
||||
omit_right=false
|
||||
# Step through pair-wise, with the directory separator ('/') being what we iterate over
|
||||
# (the array is reversed)
|
||||
# We only do one change, then bail, so we can work on the new path
|
||||
for i in "${!parts[@]}"; do
|
||||
left=${parts[$i]}
|
||||
if [[ $i -ne ${#parts[@]}-1 ]]; then
|
||||
# There is no $right for the last element
|
||||
right=${parts[$i+1]}
|
||||
else
|
||||
right=''
|
||||
fi
|
||||
if [[ $left = '.' ]]; then
|
||||
# Remove '/./'
|
||||
omit_left=true
|
||||
break
|
||||
fi
|
||||
if [[ $right = '.' ]]; then
|
||||
# Remove '/./'
|
||||
omit_right=true
|
||||
break
|
||||
fi
|
||||
if [[ $left != '..' && $right == '..' ]]; then
|
||||
# Remove 'somedir/../'
|
||||
omit_left=true
|
||||
omit_right=true
|
||||
break
|
||||
fi
|
||||
if [[ $i -eq 0 && $left = '..' && $path = /* ]]; then
|
||||
# On absolute paths, remove '/../somedir'
|
||||
omit_left=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
new_parts=()
|
||||
# Create new_parts, omitting $left and/or $right
|
||||
for j in "${!parts[@]}"; do
|
||||
[[ $omit_left = true && $j -eq $i ]] && continue
|
||||
[[ $omit_right = true && $j -eq $i+1 ]] && continue
|
||||
new_parts+=("${parts[$j]}")
|
||||
done
|
||||
parts=("${new_parts[@]}")
|
||||
if [[ $omit_left = false && $omit_right = false ]]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
# Construct $new_path from $parts
|
||||
local new_path=''
|
||||
for part in "${parts[@]}"; do
|
||||
if [[ -z $new_path ]]; then
|
||||
# Prevent leading slash
|
||||
new_path="$part"
|
||||
else
|
||||
new_path="$new_path/$part"
|
||||
fi
|
||||
done
|
||||
# Add leading slash for absolute paths
|
||||
if [[ $path = /* ]]; then
|
||||
new_path="/$new_path"
|
||||
fi
|
||||
printf "%s\n" "$new_path"
|
||||
}
|
||||
|
||||
# Determines the relative path from source_dir to target
|
||||
# As in: "What would the symlink look like if a file in $source_dir linked to $target?"
|
||||
# $source_dir is the directory in which the link resides.
|
||||
# $target is the relative path from $source_dir to it.
|
||||
# NOTE: You will get different results for the same parameters depending on
|
||||
# which path parts of $source_dir and $target are symlinks.
|
||||
# Example:
|
||||
# $source_dir: "/root/alpha/beta" ("beta" links to "../../gamma")
|
||||
# $target: "/root/delta/file"
|
||||
# result: "../root/delta/file"
|
||||
# If "/root/delta" were a symlink to "../gamma", the result would be "file"
|
||||
function create_rel_path {
|
||||
local source_dir=$1
|
||||
local target=$2
|
||||
|
||||
# Resolve symlinks in $source_dir and the parents of $target
|
||||
source_dir=$(abs_path -P "${source_dir%/}/.")/ || return $?
|
||||
target=$(abs_path -P "$target") || return $?
|
||||
|
||||
# Make sure $prefix has a trailing slash
|
||||
local prefix
|
||||
prefix=$target/
|
||||
# Find the common prefix of $source_dir and $target
|
||||
while [[ ! ${source_dir:0:${#prefix}} = "$prefix" ]]; do
|
||||
# Remove directory parts from prefix until we find the common directory
|
||||
prefix=$(dirname "$prefix")
|
||||
# Append the trailing slash, except for "/" (hence the %/)
|
||||
prefix=${prefix%/}/
|
||||
done
|
||||
# The path from the common directory to the target is
|
||||
# $target without the prefix
|
||||
local target_path
|
||||
# Check if $target = $prefix (without the trailing slash)
|
||||
if [[ $target = "${prefix%/}" ]]; then
|
||||
target_path=''
|
||||
else
|
||||
target_path=${target##$prefix}
|
||||
fi
|
||||
# The path from the common directory to the source_dir is
|
||||
# $source_dir without the prefix
|
||||
local source_dir_path
|
||||
source_dir_path=${source_dir##$prefix}
|
||||
|
||||
# Determine the path from the source_dir to the common directory (consists only of ../)
|
||||
local common_dir_path=''
|
||||
while [[ $source_dir_path != '' && $source_dir_path != '.' ]]; do
|
||||
source_dir_path=$(dirname "$source_dir_path")
|
||||
if [[ ${#common_dir_path} -eq 0 ]]; then
|
||||
common_dir_path=".."
|
||||
else
|
||||
common_dir_path="$common_dir_path/.."
|
||||
fi
|
||||
done
|
||||
|
||||
# The relative path is just the path from $source_dir to $common dir to the target
|
||||
if [[ -n $common_dir_path && -n $target_path ]]; then
|
||||
# Add dir separator if both paths are non-empty
|
||||
printf "%s/%s" "$common_dir_path" "$target_path"
|
||||
else
|
||||
printf "%s%s" "$common_dir_path" "$target_path"
|
||||
fi
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Snatched from http://stackoverflow.com/questions/4023830/bash-how-compare-two-strings-in-version-format
|
||||
function version_compare {
|
||||
if [[ $1 == "$2" ]]; then
|
||||
return 0
|
||||
fi
|
||||
local IFS=.
|
||||
# shellcheck disable=SC2206
|
||||
local i ver1=($1) ver2=($2)
|
||||
# fill empty fields in ver1 with zeros
|
||||
for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); do
|
||||
ver1[i]=0
|
||||
done
|
||||
for ((i=0; i<${#ver1[@]}; i++)); do
|
||||
if [[ -z ${ver2[i]} ]]; then
|
||||
# fill empty fields in ver2 with zeros
|
||||
ver2[i]=0
|
||||
fi
|
||||
if ((10#${ver1[i]} > 10#${ver2[i]})); then
|
||||
return 1
|
||||
fi
|
||||
if ((10#${ver1[i]} < 10#${ver2[i]})); then
|
||||
return 2
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
function repo_has_upstream {
|
||||
local repo=$1
|
||||
|
||||
# Check if the castle has an upstream remote
|
||||
# Fetch the current branch name
|
||||
local ref
|
||||
local branch
|
||||
ref=$(cd "$repo" && git symbolic-ref HEAD 2>/dev/null)
|
||||
branch=${ref#refs/heads/}
|
||||
# Get the upstream remote of that branch
|
||||
local remote_name
|
||||
remote_name=$(cd "$repo" && git config "branch.$branch.remote" 2>/dev/null)
|
||||
if [[ -z $remote_name ]]; then
|
||||
return "$EX_ERR"
|
||||
fi
|
||||
return "$EX_SUCCESS"
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Define some colors
|
||||
txtdef="\e[0m" # Revert to default
|
||||
bldred="\e[1;31m" # Red - error
|
||||
bldgrn="\e[1;32m" # Green - success
|
||||
bldylw="\e[1;33m" # Yellow - warning
|
||||
bldblu="\e[1;34m" # Blue - no action/ignored
|
||||
bldcyn="\e[1;36m" # Cyan - pending action
|
||||
bldwht="\e[1;37m" # White - info
|
||||
|
||||
function err {
|
||||
local exit_status=$1
|
||||
local reason="$2"
|
||||
shift 2
|
||||
if [[ $pending_status ]]; then
|
||||
# no args for fail
|
||||
# shellcheck disable=SC2119
|
||||
fail
|
||||
fi
|
||||
status "$bldred" "error" "$reason" >&2
|
||||
if [[ $# -gt 0 ]]; then
|
||||
printf "%s\n" "$@" >&2
|
||||
fi
|
||||
exit "$exit_status"
|
||||
}
|
||||
|
||||
function help_err {
|
||||
# shellcheck source=lib/commands/help.sh disable=SC2154
|
||||
source "$homeshick/lib/commands/help.sh"
|
||||
extended_help "$1"
|
||||
exit "$EX_USAGE"
|
||||
}
|
||||
|
||||
function status {
|
||||
if $TALK; then
|
||||
printf "$1%13s$txtdef %s\n" "$2" "$3"
|
||||
fi
|
||||
}
|
||||
|
||||
function warn {
|
||||
status "$bldylw" "$1" "$2"
|
||||
}
|
||||
|
||||
function info {
|
||||
status "$bldwht" "$1" "$2"
|
||||
}
|
||||
|
||||
pending_status=''
|
||||
pending_message=''
|
||||
function pending {
|
||||
pending_status="$1"
|
||||
pending_message="$2"
|
||||
if $TALK; then
|
||||
printf "$bldcyn%13s$txtdef %s" "$pending_status" "$pending_message"
|
||||
fi
|
||||
}
|
||||
|
||||
# fail is used globally
|
||||
# shellcheck disable=SC2120
|
||||
function fail {
|
||||
[[ $1 ]] && pending_status=$1
|
||||
[[ $2 ]] && pending_message=$2
|
||||
status "\r$bldred" "$pending_status" "$pending_message"
|
||||
unset pending_status pending_message
|
||||
}
|
||||
|
||||
function ignore {
|
||||
[[ $1 ]] && pending_status=$1
|
||||
[[ $2 ]] && pending_message=$2
|
||||
status "\r$bldblu" "$pending_status" "$pending_message"
|
||||
unset pending_status pending_message
|
||||
}
|
||||
|
||||
function success {
|
||||
[[ $1 ]] && pending_status=$1
|
||||
[[ $2 ]] && pending_message=$2
|
||||
status "\r$bldgrn" "$pending_status" "$pending_message"
|
||||
unset pending_status pending_message
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
#!/bin/bash
|
||||
|
||||
function ask_symlink {
|
||||
if [[ $# -gt 0 ]]; then
|
||||
if [[ $# == 1 ]]; then
|
||||
msg="The castle $1 has new files."
|
||||
else
|
||||
OIFS=$IFS
|
||||
IFS=,
|
||||
msg="The castles $* have new files."
|
||||
IFS=$OIFS
|
||||
fi
|
||||
if prompt_no 'updates' "$msg" 'symlink?'; then
|
||||
# shellcheck source=lib/commands/link.sh disable=SC2154
|
||||
source "$homeshick/lib/commands/link.sh"
|
||||
for castle in "$@"; do
|
||||
symlink "$castle"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
return "$EX_SUCCESS"
|
||||
}
|
||||
|
||||
|
||||
# Singleline prompt that stays on the same line even if you press enter.
|
||||
# Automatically colors the line according to the answer the user gives.
|
||||
# Currently homeshick only has prompts with "no" as the default,
|
||||
# so there's no reason to implement prompt_yes right now
|
||||
function prompt_no {
|
||||
local OTALK=$TALK
|
||||
# Disable the quiet flag while prompting in interactive mode
|
||||
if ! $BATCH; then
|
||||
TALK=true
|
||||
fi
|
||||
|
||||
local status=$1
|
||||
local message=$2
|
||||
local prompt=$3
|
||||
local result=-1
|
||||
|
||||
# global vars
|
||||
# shellcheck disable=SC2154
|
||||
status "$bldwht" "$status" "$message"
|
||||
if ! $BATCH; then
|
||||
pending "$prompt" "[yN] "
|
||||
while true; do
|
||||
local answer=""
|
||||
local char=""
|
||||
while true; do
|
||||
read -s -n 1 -r char
|
||||
if [[ $char == "" ]]; then
|
||||
break
|
||||
fi
|
||||
printf "%c" "$char"
|
||||
answer="${answer}${char}"
|
||||
done
|
||||
case $answer in
|
||||
Y|y) result=0 ;;
|
||||
N|n) result=1 ;;
|
||||
"") result=2 ;;
|
||||
esac
|
||||
[[ $result -ge 0 ]] && break
|
||||
for (( i=0; i<${#answer}; i++ )) ; do
|
||||
printf "\b"
|
||||
done
|
||||
printf "%${#answer}s\r"
|
||||
# global vars
|
||||
# shellcheck disable=SC2154
|
||||
pending "$pending_status" "$pending_message"
|
||||
done
|
||||
else
|
||||
pending "$prompt" "BATCH - No"
|
||||
result=2
|
||||
fi
|
||||
if [[ $result == 0 ]]; then
|
||||
success
|
||||
else
|
||||
fail
|
||||
fi
|
||||
TALK=$OTALK
|
||||
return $result
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
repos
|
||||
home
|
||||
nothome
|
||||
shunit2-*
|
||||
setup.sh
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
SCRIPTDIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||
export SCRIPTDIR
|
||||
|
||||
if [[ -n $1 ]]; then
|
||||
if [[ $1 == '--help' ]]; then
|
||||
printf "Run homeshick working copy interactively\n"
|
||||
printf " Usage: interactive [shell]\n"
|
||||
exit 0
|
||||
else
|
||||
if [[ $# -gt 1 ]]; then
|
||||
printf "only one SHELL argument allowed\n" >&2
|
||||
exit 64 # 64=$EX_USAGE
|
||||
fi
|
||||
_shell=$1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z $_shell ]]; then
|
||||
_shell=$SHELL
|
||||
fi
|
||||
|
||||
# Replacement function for the run fn in bats
|
||||
# Disable SC2034 for global variables
|
||||
# shellcheck disable=SC2034
|
||||
run() {
|
||||
local e E T
|
||||
[[ ! "$-" =~ e ]] || e=1
|
||||
[[ ! "$-" =~ E ]] || E=1
|
||||
[[ ! "$-" =~ T ]] || T=1
|
||||
set +e
|
||||
set +E
|
||||
set +T
|
||||
output="$("$@" 2>&1)"
|
||||
status="$?"
|
||||
# shellcheck disable=SC2206
|
||||
IFS=$'\n' lines=($output)
|
||||
[ -z "$e" ] || set -e
|
||||
[ -z "$E" ] || set -E
|
||||
[ -z "$T" ] || set -T
|
||||
}
|
||||
# shellcheck source=test/helper.bash
|
||||
source "$SCRIPTDIR/helper.bash"
|
||||
export_env_vars
|
||||
setup_env
|
||||
ln_homeshick
|
||||
|
||||
setup_script="$SCRIPTDIR/setup.sh"
|
||||
if [[ -f $setup_script ]]; then
|
||||
$setup_script
|
||||
else
|
||||
"$SCRIPTDIR/setup-default.sh"
|
||||
fi
|
||||
|
||||
cd "$HOME"
|
||||
/usr/bin/env "$_shell"
|
||||
rm_structure
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
SCRIPTDIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||
bats "${SCRIPTDIR}/suites"
|
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env bash
|
||||
# shellcheck disable=SC2016
|
||||
printf '\nsource "$HOME/.homesick/repos/homeshick/homeshick.sh"' >> "$HOME/.bashrc"
|
||||
cat > "$HOME/.bashrc" <<EOF
|
||||
source "$HOME/.homesick/repos/homeshick/homeshick.sh"
|
||||
source "\$HOME/.homesick/repos/homeshick/completions/homeshick-completion.bash"
|
||||
EOF
|
||||
cat > "$HOME/.zshrc" <<EOF
|
||||
source "\$HOME/.homesick/repos/homeshick/homeshick.sh"
|
||||
fpath=(\$HOME/.homesick/repos/homeshick/completions \$fpath)
|
||||
autoload -U compinit
|
||||
compinit
|
||||
EOF
|
||||
printf '\nalias homeshick source "$HOME/.homesick/repos/homeshick/homeshick.csh"' >> "$HOME/.cshrc"
|
||||
mkdir -p "$HOME/.config/fish"
|
||||
printf '\nsource "$HOME/.homesick/repos/homeshick/homeshick.fish"' >> "$HOME/.config/fish/config.fish"
|
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
shopt -s extglob
|
||||
shopt -s nullglob
|
||||
|
||||
shellcheck --shell=bash \
|
||||
bin/homeshick \
|
||||
lib/!(exit_status).sh \
|
||||
{,lib/commands/,test/,test/fixtures/}*.{sh,bash} \
|
||||
test/{run,interactive,shellcheck}
|
||||
|
||||
shellcheck --shell=sh homeshick.sh
|
||||
shellcheck --shell=bash homeshick.sh
|
||||
shellcheck --shell=dash homeshick.sh
|
||||
shellcheck --shell=ksh homeshick.sh
|
|
@ -0,0 +1,17 @@
|
|||
#!/bin/bash
|
||||
|
||||
# shellcheck disable=2164
|
||||
function fixture_123.abc() {
|
||||
local git_username="Homeshick user"
|
||||
local git_useremail="homeshick@example.com"
|
||||
local weirdname="$REPO_FIXTURES/135.abc"
|
||||
git init "$weirdname"
|
||||
cd "$weirdname"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
touch .gitkeep
|
||||
git add .gitkeep
|
||||
git commit -m 'Add file to repo with weird name'
|
||||
}
|
||||
|
||||
fixture_123.abc > /dev/null
|
|
@ -0,0 +1,82 @@
|
|||
#!/bin/bash
|
||||
|
||||
# shellcheck disable=2164
|
||||
function fixture_dotfiles() {
|
||||
local git_username="Homeshick user"
|
||||
local git_useremail="homeshick@example.com"
|
||||
local dotfiles="$REPO_FIXTURES/dotfiles"
|
||||
git init "$dotfiles"
|
||||
cd "$dotfiles"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
mkdir home
|
||||
cd home
|
||||
|
||||
mkdir -p .config/foo.conf
|
||||
cat > .config/foo.conf/a.conf <<EOF
|
||||
#I am just a regular config file
|
||||
[A]
|
||||
LikesIceCream=True
|
||||
EOF
|
||||
cat > .config/bar.dir <<EOF
|
||||
#And I am just a regular config file with a weird name
|
||||
[B]
|
||||
LikesCucumber=False
|
||||
EOF
|
||||
# Do not put anything in .config/foo
|
||||
# it is meant to be a directory
|
||||
# only containing other directories
|
||||
mkdir -p .config/foo/bar
|
||||
touch .config/foo/bar/baz.conf
|
||||
git add .config
|
||||
git commit -m 'Files added for my new dotfiles repo'
|
||||
|
||||
mkdir .ssh
|
||||
cat > .ssh/known_hosts <<EOF
|
||||
github.com,207.97.227.239 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
|
||||
EOF
|
||||
git add .ssh
|
||||
git commit -m 'Share known_hosts across machines'
|
||||
|
||||
local st3_settings="Library/Application Support/Sublime Text 3/Packages/User"
|
||||
mkdir -p "$st3_settings"
|
||||
cat > "$st3_settings/Preferences.sublime-settings" <<EOF
|
||||
{
|
||||
"caret_style": "wide",
|
||||
"default_line_ending": "unix",
|
||||
"ensure_newline_at_eof_on_save": true,
|
||||
"font_face": "Inconsolata-dz",
|
||||
"rulers": [110],
|
||||
"shift_tab_unindent": true,
|
||||
"tab_size": 2
|
||||
}
|
||||
EOF
|
||||
git add "$st3_settings/Preferences.sublime-settings"
|
||||
git commit -m 'Added my Sublime Text 3 settings'
|
||||
|
||||
local dotfiles_vim_submodule="$REPO_FIXTURES/dotfiles_vim_submodule"
|
||||
(
|
||||
git init "$dotfiles_vim_submodule"
|
||||
cd "$dotfiles_vim_submodule"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
|
||||
mkdir -p autoload
|
||||
cat > autoload/pathogen.vim <<EOF
|
||||
dummy pathogen autloader file
|
||||
EOF
|
||||
|
||||
mkdir -p bundles/vim-git
|
||||
cat > bundles/vim-git/README.md <<EOF
|
||||
Just a random README for the dummy vim-git bundle
|
||||
EOF
|
||||
git add autoload bundles
|
||||
git commit -m 'vim-git bundle for my vim config'
|
||||
)
|
||||
|
||||
cd "$dotfiles"
|
||||
git submodule add "$dotfiles_vim_submodule" home/.vim
|
||||
git commit -m 'New vim configuration submodule'
|
||||
}
|
||||
|
||||
fixture_dotfiles > /dev/null
|
|
@ -0,0 +1,37 @@
|
|||
#!/bin/bash
|
||||
|
||||
# shellcheck disable=2164
|
||||
function fixture_module_files() {
|
||||
local git_username="Homeshick user"
|
||||
local git_useremail="homeshick@example.com"
|
||||
local module_files="$REPO_FIXTURES/module-files"
|
||||
git init "$module_files"
|
||||
cd "$module_files"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
|
||||
local my_module="$REPO_FIXTURES/my_module"
|
||||
(
|
||||
git init "$my_module"
|
||||
cd "$my_module"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
cat > module_foo.conf <<EOF
|
||||
# my module file
|
||||
EOF
|
||||
git add module_foo.conf
|
||||
git commit -m 'module_foo file for new my_module repo'
|
||||
)
|
||||
|
||||
|
||||
git submodule add "$my_module"
|
||||
git commit -m 'my_module (git submodule) added for my new module-files repo'
|
||||
|
||||
mkdir home
|
||||
cd home
|
||||
ln -s ../my_module .my_module
|
||||
git add .my_module
|
||||
git commit -m 'Files added for my new module-files repo'
|
||||
}
|
||||
|
||||
fixture_module_files > /dev/null
|
|
@ -0,0 +1,99 @@
|
|||
#!/bin/bash
|
||||
|
||||
# shellcheck disable=2164
|
||||
function fixture_nested_submodules() {
|
||||
local git_username="Homeshick user"
|
||||
local git_useremail="homeshick@example.com"
|
||||
local nested_submodules="$REPO_FIXTURES/nested-submodules"
|
||||
git init "$nested_submodules"
|
||||
cd "$nested_submodules"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
cat > info <<EOF
|
||||
This is level0 of the nested submodule repo
|
||||
EOF
|
||||
git add info
|
||||
git commit -m 'Add info file for level0 repo'
|
||||
|
||||
local level1="$REPO_FIXTURES/level1"
|
||||
(
|
||||
git init "$level1"
|
||||
cd "$level1"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
cat > info <<EOF
|
||||
This is level1 of the nested submodule repo
|
||||
EOF
|
||||
git add info
|
||||
git commit -m 'Add info file for level1 repo'
|
||||
|
||||
local level2="$REPO_FIXTURES/level2"
|
||||
(
|
||||
git init "$level2"
|
||||
cd "$level2"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
cat > info <<EOF
|
||||
This is level2 of the nested submodule repo
|
||||
EOF
|
||||
git add info
|
||||
git commit -m 'Add info file for level2 repo'
|
||||
)
|
||||
|
||||
git submodule add "$level2" level2
|
||||
git commit -m 'level2 submodule added for level1'
|
||||
)
|
||||
|
||||
git submodule add "$level1" level1
|
||||
git commit -m 'level1 submodule added for level0'
|
||||
|
||||
local home="$REPO_FIXTURES/home-for-nested-submodule"
|
||||
(
|
||||
git init "$home"
|
||||
cd "$home"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
cat > .info <<EOF
|
||||
This is level "home" of the nested submodule repo
|
||||
EOF
|
||||
git add .info
|
||||
git commit -m 'Add info file for home repo'
|
||||
|
||||
local homesub="$REPO_FIXTURES/home-subdir-for-nested-submodule"
|
||||
(
|
||||
git init "$homesub"
|
||||
cd "$homesub"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
cat > .info1 <<EOF
|
||||
This is level "homesub" of the nested submodule repo
|
||||
EOF
|
||||
git add .info1
|
||||
git commit -m 'Add info file for homesub repo'
|
||||
|
||||
local subsub="$REPO_FIXTURES/sub-subdir-for-nested-submodule"
|
||||
(
|
||||
git init "$subsub"
|
||||
cd "$subsub"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
cat > .info2 <<EOF
|
||||
This is level "subsub" of the nested submodule repo
|
||||
EOF
|
||||
git add .info2
|
||||
git commit -m 'Add info file for subsub repo'
|
||||
)
|
||||
|
||||
git submodule add "$subsub" .subdir2
|
||||
git commit -m 'subsub submodule added for level2'
|
||||
)
|
||||
|
||||
git submodule add "$homesub" .subdir1
|
||||
git commit -m 'homesub submodule added for level1'
|
||||
)
|
||||
|
||||
git submodule add "$home" home
|
||||
git commit -m 'home submodule added for level0'
|
||||
}
|
||||
|
||||
fixture_nested_submodules > /dev/null
|
|
@ -0,0 +1,20 @@
|
|||
#!/bin/bash
|
||||
|
||||
# shellcheck disable=2164
|
||||
function fixture_nodirs() {
|
||||
local git_username="Homeshick user"
|
||||
local git_useremail="homeshick@example.com"
|
||||
local nodirs="$REPO_FIXTURES/nodirs"
|
||||
git init "$nodirs"
|
||||
cd "$nodirs"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
mkdir home
|
||||
cd home
|
||||
|
||||
touch .file1
|
||||
git add .file1
|
||||
git commit -m 'Add .file1 to test listing files'
|
||||
}
|
||||
|
||||
fixture_nodirs > /dev/null
|
|
@ -0,0 +1,50 @@
|
|||
#!/bin/bash
|
||||
|
||||
# shellcheck disable=2164
|
||||
function fixture_rc_files() {
|
||||
local git_username="Homeshick user"
|
||||
local git_useremail="homeshick@example.com"
|
||||
local rc_files="$REPO_FIXTURES/rc-files"
|
||||
git init "$rc_files"
|
||||
cd "$rc_files"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
mkdir home
|
||||
cd home
|
||||
|
||||
cat > .bashrc <<EOF
|
||||
#!/bin/bash
|
||||
PS1='\[33[01;32m\]\u@\h\[33[00m\]:\[33[01;34m\]\w\'
|
||||
EOF
|
||||
git add .bashrc
|
||||
git commit -m '.bashrc file for my new rc-files repo'
|
||||
|
||||
cat > "$NOTHOME/some-file" <<EOF
|
||||
File with some content.
|
||||
EOF
|
||||
ln -s "$NOTHOME/some-file" symlinked-file
|
||||
git add symlinked-file
|
||||
git commit -m 'Added a symlinked file'
|
||||
|
||||
mkdir "$NOTHOME/some-directory"
|
||||
ln -s "$NOTHOME/some-directory" symlinked-directory
|
||||
git add symlinked-directory
|
||||
git commit -m 'Added a symlinked directory'
|
||||
|
||||
ln -s "$NOTHOME/nonexistent" dead-symlink
|
||||
git add dead-symlink
|
||||
git commit -m 'Added a dead symlink'
|
||||
|
||||
# Create a branch with a slash in it.
|
||||
# Used for list suite unit test testSlashInBranch()
|
||||
git branch branch/with/slash
|
||||
|
||||
cat > .gitignore <<EOF
|
||||
.DS_Store
|
||||
*.swp
|
||||
EOF
|
||||
git add .gitignore
|
||||
git commit -m 'Added .gitignore file'
|
||||
}
|
||||
|
||||
fixture_rc_files > /dev/null
|
|
@ -0,0 +1,35 @@
|
|||
#!/bin/bash
|
||||
|
||||
# shellcheck disable=2164
|
||||
function fixture_repo_with_spaces_in_name() {
|
||||
local git_username="Homeshick user"
|
||||
local git_useremail="homeshick@example.com"
|
||||
local namewithspaces="${REPO_FIXTURES}/repo with spaces in name"
|
||||
git init "$namewithspaces"
|
||||
cd "$namewithspaces"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
mkdir home
|
||||
cd home
|
||||
|
||||
touch .repowithspacesfile
|
||||
|
||||
git add .repowithspacesfile
|
||||
git commit -m 'Add file to repo with spaces in name'
|
||||
|
||||
touch ".file with spaces in name"
|
||||
git add ".file with spaces in name"
|
||||
|
||||
mkdir ".folder with spaces in name"
|
||||
touch ".folder with spaces in name/another file with spaces in its name"
|
||||
git add ".folder with spaces in name/another file with spaces in its name"
|
||||
|
||||
touch ".crazy
|
||||
file␇☺"
|
||||
git add ".crazy
|
||||
file␇☺"
|
||||
|
||||
git commit -m 'Add file with newline and all kinds of crazy characters in the name'
|
||||
}
|
||||
|
||||
fixture_repo_with_spaces_in_name > /dev/null
|
|
@ -0,0 +1,37 @@
|
|||
#!/bin/bash
|
||||
|
||||
# shellcheck disable=2164
|
||||
function fixture_submodule_outside_home() {
|
||||
local git_username="Homeshick user"
|
||||
local git_useremail="homeshick@example.com"
|
||||
local submodule_outside_home_files="$REPO_FIXTURES/submodule-outside-home"
|
||||
git init "$submodule_outside_home_files"
|
||||
cd "$submodule_outside_home_files"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
|
||||
local the_submodule="$REPO_FIXTURES/the-submodule"
|
||||
(
|
||||
git init "$the_submodule"
|
||||
cd "$the_submodule"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
cat > somefile.conf <<EOF
|
||||
some kind of file
|
||||
EOF
|
||||
git add somefile.conf
|
||||
git commit -m 'added some kind of file to the-submodule'
|
||||
)
|
||||
|
||||
|
||||
git submodule add "$the_submodule"
|
||||
git commit -m 'the-submodule (git submodule) added for my new module-files repo'
|
||||
|
||||
mkdir home
|
||||
cd home
|
||||
ln -s .. root
|
||||
git add root
|
||||
git commit -m 'recursive symlink added'
|
||||
}
|
||||
|
||||
fixture_submodule_outside_home > /dev/null
|
|
@ -0,0 +1,20 @@
|
|||
#!/bin/bash
|
||||
|
||||
# shellcheck disable=2164
|
||||
function fixture_symlinks() {
|
||||
local git_username="Homeshick user"
|
||||
local git_useremail="homeshick@example.com"
|
||||
local symlinks="$REPO_FIXTURES/symlinks"
|
||||
git init "$symlinks"
|
||||
cd "$symlinks"
|
||||
git config user.name "$git_username"
|
||||
git config user.email "$git_useremail"
|
||||
mkdir home
|
||||
cd home
|
||||
|
||||
ln -s ../../../../file_in_homedir link_to_homedir_file
|
||||
git add link_to_homedir_file
|
||||
git commit -m 'Add file to test symlinking'
|
||||
}
|
||||
|
||||
fixture_symlinks > /dev/null
|
|
@ -0,0 +1,161 @@
|
|||
#!/bin/bash
|
||||
|
||||
function export_env_vars {
|
||||
if [[ -n $BATS_TEST_DIRNAME ]]; then
|
||||
TESTDIR=$(cd "$BATS_TEST_DIRNAME/.." && pwd)
|
||||
else
|
||||
TESTDIR=$(cd "$SCRIPTDIR" && pwd)
|
||||
fi
|
||||
export TESTDIR
|
||||
_TMPDIR=$(mktemp -d 2>/dev/null || mktemp -d -t homeshick)
|
||||
export _TMPDIR
|
||||
export REPO_FIXTURES="$_TMPDIR/repos"
|
||||
export HOME="$_TMPDIR/home"
|
||||
export NOTHOME="$_TMPDIR/nothome"
|
||||
export HOMESICK="$HOME/.homesick"
|
||||
|
||||
export HOMESHICK_FN="homeshick"
|
||||
local repo_dir
|
||||
repo_dir=$(cd "$TESTDIR/.." && pwd)
|
||||
export HOMESHICK_DIR=${HOMESHICK_DIR:-$repo_dir}
|
||||
export HOMESHICK_FN_SRC_SH="$HOMESHICK_DIR/homeshick.sh"
|
||||
export HOMESHICK_FN_SRC_FISH="$HOMESHICK_DIR/homeshick.fish"
|
||||
export HOMESHICK_FN_SRC_CSH="$HOMESHICK_DIR/homeshick.csh"
|
||||
export HOMESHICK_BIN="$HOMESHICK_DIR/bin/homeshick"
|
||||
|
||||
# Check if expect is installed
|
||||
if type expect &>/dev/null; then
|
||||
export EXPECT_INSTALLED=true
|
||||
else
|
||||
export EXPECT_INSTALLED=false
|
||||
fi
|
||||
}
|
||||
|
||||
function remove_coreutils_from_path {
|
||||
# Check if coreutils is in PATH
|
||||
system=$(uname -a)
|
||||
if [[ $system =~ "Darwin" && ! $system =~ "AppleTV" ]]; then
|
||||
if type brew &>/dev/null; then
|
||||
coreutils_path=$(brew --prefix coreutils 2>/dev/null)/libexec/gnubin
|
||||
if [[ -d $coreutils_path && $PATH == *$coreutils_path* ]]; then
|
||||
if [[ -z $HOMESHICK_KEEP_PATH || $HOMESHICK_KEEP_PATH == false ]]; then
|
||||
export PATH=${PATH//$coreutils_path/''}
|
||||
export PATH=${PATH//'::'/':'} # Remove any left over colons
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function mk_structure {
|
||||
mkdir "$REPO_FIXTURES" "$HOME" "$NOTHOME"
|
||||
}
|
||||
|
||||
function ln_homeshick {
|
||||
local hs_repo=$HOMESICK/repos/homeshick
|
||||
mkdir -p "$hs_repo"
|
||||
local repo_dir
|
||||
repo_dir=$(cd "$TESTDIR/.." && pwd)
|
||||
ln -s "$repo_dir/homeshick.sh" "$hs_repo/homeshick.sh"
|
||||
ln -s "$repo_dir/homeshick.csh" "$hs_repo/homeshick.csh"
|
||||
ln -s "$repo_dir/homeshick.fish" "$hs_repo/homeshick.fish"
|
||||
ln -s "$repo_dir/bin" "$hs_repo/bin"
|
||||
ln -s "$repo_dir/lib" "$hs_repo/lib"
|
||||
ln -s "$repo_dir/completions" "$hs_repo/completions"
|
||||
}
|
||||
|
||||
function rm_structure {
|
||||
# Make sure _TMPDIR wasn't unset
|
||||
[[ -n $_TMPDIR ]] && rm -rf "$_TMPDIR"
|
||||
}
|
||||
|
||||
function setup_env {
|
||||
remove_coreutils_from_path
|
||||
export_env_vars
|
||||
mk_structure
|
||||
# shellcheck source=homeshick.sh
|
||||
source "$HOMESHICK_FN_SRC_SH"
|
||||
}
|
||||
|
||||
function setup {
|
||||
setup_env
|
||||
}
|
||||
|
||||
function teardown {
|
||||
rm_structure
|
||||
}
|
||||
|
||||
function fixture {
|
||||
local name=$1
|
||||
if [[ ! -e "$REPO_FIXTURES/$name" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$TESTDIR/fixtures/$name.sh"
|
||||
fi
|
||||
}
|
||||
|
||||
function castle {
|
||||
local fixture_name=$1
|
||||
fixture "$fixture_name"
|
||||
$HOMESHICK_FN --batch clone "$REPO_FIXTURES/$fixture_name" > /dev/null
|
||||
}
|
||||
|
||||
function is_symlink {
|
||||
expected=$1
|
||||
path=$2
|
||||
target=$(readlink "$path")
|
||||
[ "$expected" = "$target" ]
|
||||
}
|
||||
|
||||
function get_inode_no {
|
||||
stat -c %i "$1" 2>/dev/null || stat -f %i "$1"
|
||||
}
|
||||
|
||||
# Snatched from http://stackoverflow.com/questions/4023830/bash-how-compare-two-strings-in-version-format
|
||||
function version_compare {
|
||||
if [[ $1 == "$2" ]]; then
|
||||
return 0
|
||||
fi
|
||||
local IFS=.
|
||||
# shellcheck disable=SC2206
|
||||
local i ver1=($1) ver2=($2)
|
||||
# fill empty fields in ver1 with zeros
|
||||
for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); do
|
||||
ver1[i]=0
|
||||
done
|
||||
for ((i=0; i<${#ver1[@]}; i++)); do
|
||||
if [[ -z ${ver2[i]} ]]; then
|
||||
# fill empty fields in ver2 with zeros
|
||||
ver2[i]=0
|
||||
fi
|
||||
if ((10#${ver1[i]} > 10#${ver2[i]})); then
|
||||
return 1
|
||||
fi
|
||||
if ((10#${ver1[i]} < 10#${ver2[i]})); then
|
||||
return 2
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
function get_git_version {
|
||||
if [[ -z $GIT_VERSION ]]; then
|
||||
read -r _ _ GIT_VERSION _ < <(command git --version)
|
||||
if [[ ! $GIT_VERSION =~ ([0-9]+)(\.[0-9]+){0,3} ]]; then
|
||||
skip 'could not detect git version'
|
||||
fi
|
||||
fi
|
||||
printf "%s" "$GIT_VERSION"
|
||||
}
|
||||
|
||||
function commit_repo_state {
|
||||
local repo=$1
|
||||
(
|
||||
# Let cd just fail
|
||||
# shellcheck disable=SC2164
|
||||
cd "$repo"
|
||||
git config user.name "Homeshick user"
|
||||
git config user.email "homeshick@example.com"
|
||||
git add -A
|
||||
git commit -m "Commiting Repo State from test helper.bash."
|
||||
)
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
function setup() {
|
||||
setup_env
|
||||
source "$HOMESHICK_DIR/lib/fs.sh"
|
||||
}
|
||||
|
||||
@test 'test simple filepath' {
|
||||
touch "$HOME/file"
|
||||
local path
|
||||
path=$(cd "$HOME" && abs_path file)
|
||||
[ "$path" = "$HOME/file" ]
|
||||
}
|
||||
|
||||
@test 'test filepath with spaces' {
|
||||
mkdir "$HOME/folder with spaces"
|
||||
touch "$HOME/folder with spaces/file"
|
||||
local path
|
||||
path=$(cd "$HOME" && abs_path folder\ with\ spaces/file)
|
||||
[ "$path" = "$HOME/folder with spaces/file" ]
|
||||
}
|
||||
|
||||
@test 'test filepath and filename with spaces' {
|
||||
mkdir "$HOME/folder with spaces"
|
||||
touch "$HOME/folder with spaces/file name with spaces"
|
||||
local path
|
||||
path=$(cd "$HOME" && abs_path folder\ with\ spaces/file\ name\ with\ spaces)
|
||||
[ "$path" = "$HOME/folder with spaces/file name with spaces" ]
|
||||
}
|
||||
|
||||
@test 'test folder' {
|
||||
mkdir "$HOME/folder"
|
||||
local path
|
||||
path=$(cd "$HOME" && abs_path folder)
|
||||
[ "$path" = "$HOME/folder" ]
|
||||
}
|
||||
|
||||
@test 'test subfolder' {
|
||||
mkdir -p "$HOME/folder/subfolder"
|
||||
local path
|
||||
path=$(cd "$HOME" && abs_path folder/subfolder)
|
||||
[ "$path" = "$HOME/folder/subfolder" ]
|
||||
}
|
||||
|
||||
@test 'test folders with spaces' {
|
||||
mkdir -p "$HOME/folder with spaces/sub folder"
|
||||
local path
|
||||
path=$(cd "$HOME" && abs_path folder\ with\ spaces/sub\ folder)
|
||||
[ "$path" = "$HOME/folder with spaces/sub folder" ]
|
||||
}
|
||||
|
||||
@test 'test root' {
|
||||
local path
|
||||
path=$(abs_path /)
|
||||
[ "$path" = "/" ]
|
||||
}
|
||||
|
||||
@test 'test file in root' {
|
||||
local path
|
||||
path=$(abs_path /test)
|
||||
[ "$path" = "/test" ]
|
||||
}
|
||||
|
||||
@test 'test trailing slash' {
|
||||
local path
|
||||
path=$(abs_path /test/)
|
||||
[ "$path" = "/test" ]
|
||||
}
|
||||
|
||||
@test 'test trailing slashdot' {
|
||||
mkdir "$HOME/test"
|
||||
local path
|
||||
path=$(cd "$HOME" && abs_path test/.)
|
||||
[ "$path" = "$HOME/test" ]
|
||||
}
|
||||
|
||||
@test 'test symlink' {
|
||||
mkdir "$HOME/realdir"
|
||||
ln -s realdir "$HOME/symlink"
|
||||
local path
|
||||
path=$(cd "$HOME" && abs_path symlink)
|
||||
[ "$path" = "$HOME/symlink" ]
|
||||
}
|
||||
|
||||
@test 'test symlink resolution' {
|
||||
mkdir "$HOME/realdir"
|
||||
ln -s realdir "$HOME/symlink"
|
||||
local path
|
||||
path=$(cd "$HOME" && abs_path -P symlink/.)
|
||||
local abs_home
|
||||
abs_home=$(cd "$HOME" >/dev/null && pwd -P)
|
||||
[ "$path" = "$abs_home/realdir" ]
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
@test 'cd to dotfiles castle' {
|
||||
castle 'dotfiles'
|
||||
local dotfiles_dir=$HOMESICK/repos/dotfiles
|
||||
local result
|
||||
result=$($HOMESHICK_FN cd dotfiles && pwd)
|
||||
[ "$dotfiles_dir" = "$result" ]
|
||||
}
|
||||
|
||||
@test 'cd to dotfiles castle subdirectory' {
|
||||
castle 'dotfiles'
|
||||
local dotfiles_dir=$HOMESICK/repos/dotfiles/home/.config/foo.conf
|
||||
local result
|
||||
result=$($HOMESHICK_FN cd dotfiles/home/.config/foo.conf && pwd)
|
||||
[ "$dotfiles_dir" = "$result" ]
|
||||
}
|
||||
|
||||
@test 'cd to my_module castle' {
|
||||
castle 'module-files'
|
||||
$HOMESHICK_FN --batch clone "$REPO_FIXTURES/my_module"
|
||||
local my_module_dir=$HOMESICK/repos/my_module
|
||||
local result
|
||||
result=$($HOMESHICK_FN cd my_module && pwd)
|
||||
[ "$my_module_dir" = "$result" ]
|
||||
}
|
||||
|
||||
@test 'cd to nonexistent castle' {
|
||||
local current_dir=$PWD
|
||||
local result
|
||||
result=$($HOMESHICK_FN cd non_existent 2>/dev/null; pwd)
|
||||
[ "$current_dir" = "$result" ]
|
||||
}
|
||||
|
||||
@test "cd'ing to nonexistent castle exits with errcode 1" {
|
||||
run "$HOMESHICK_FN" cd non_existent
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test 'cd to castle with spaces in its name' {
|
||||
castle 'repo with spaces in name'
|
||||
local spaces_repo_dir="$HOMESICK/repos/repo with spaces in name"
|
||||
local result
|
||||
result=$($HOMESHICK_FN cd repo\ with\ spaces\ in\ name && pwd)
|
||||
[ "$spaces_repo_dir" = "$result" ]
|
||||
}
|
||||
|
||||
@test 'cd to castle in sh' {
|
||||
[ "$(type -t sh)" = "file" ] || skip "sh not installed"
|
||||
castle 'dotfiles'
|
||||
local dotfiles_dir=$HOMESICK/repos/dotfiles
|
||||
cmd=". "$HOMESHICK_FN_SRC_SH" && $HOMESHICK_FN cd dotfiles && echo \$PWD"
|
||||
local result
|
||||
result=$( sh <<< "$cmd" 2>&1 )
|
||||
[ "$dotfiles_dir" = "$result" ]
|
||||
}
|
||||
|
||||
@test 'cd to castle in dash' {
|
||||
[ "$(type -t dash)" = "file" ] || skip "dash not installed"
|
||||
castle 'dotfiles'
|
||||
local dotfiles_dir=$HOMESICK/repos/dotfiles
|
||||
cmd=". "$HOMESHICK_FN_SRC_SH" && $HOMESHICK_FN cd dotfiles && echo \$PWD"
|
||||
local result
|
||||
result=$( dash <<< "$cmd" 2>&1 )
|
||||
[ "$dotfiles_dir" = "$result" ]
|
||||
}
|
||||
|
||||
@test 'cd to castle in csh' {
|
||||
[ "$(type -t csh)" = "file" ] || skip "csh not installed"
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
# in csh we can't alias a command and use that command on the same line
|
||||
# % do_something; do_something_else
|
||||
# is apparently different from
|
||||
# % do_something
|
||||
# % do_something_else
|
||||
castle 'dotfiles'
|
||||
local dotfiles_dir=$HOMESICK/repos/dotfiles
|
||||
cat <<EOF | expect -f -
|
||||
spawn csh
|
||||
send "alias $HOMESHICK_FN source \"$HOMESHICK_FN_SRC_CSH\"\n"
|
||||
send "$HOMESHICK_FN cd dotfiles\n"
|
||||
send "pwd\n"
|
||||
expect "*$dotfiles_dir*" {} default {exit 1}
|
||||
send "exit\n"
|
||||
expect EOF
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'cd to castle in fish' {
|
||||
[ "$(type -t fish)" = "file" ] || skip "fish not installed"
|
||||
castle 'dotfiles'
|
||||
# fish $PWD has all symlinks resolved
|
||||
local dotfiles_dir=$(cd "$HOMESICK/repos/dotfiles" && pwd -P)
|
||||
cmd="source "$HOMESHICK_FN_SRC_FISH"; and $HOMESHICK_FN cd dotfiles; and pwd"
|
||||
local result
|
||||
result=$( fish <<< "$cmd" 2>&1 )
|
||||
[ "$dotfiles_dir" = "$result" ]
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
function add_new_file_to_castle {
|
||||
cd "$HOMESICK/repos/rc-files" || return $?
|
||||
touch homeshick_new_file_bats_test
|
||||
}
|
||||
|
||||
function add_empty_folder_to_castle {
|
||||
cd "$HOMESICK/repos/rc-files" || return $?
|
||||
mkdir homeshick_new_folder_bats_test
|
||||
}
|
||||
|
||||
function modify_file_in_castle {
|
||||
cd "$HOMESICK/repos/rc-files" || return $?
|
||||
echo modify >> "$(find home -type f | head -1)"
|
||||
}
|
||||
|
||||
function delete_file_in_castle {
|
||||
cd "$HOMESICK/repos/rc-files" || return $?
|
||||
rm "$(find home -type f | head -1)"
|
||||
}
|
||||
|
||||
function add_commit_to_castle {
|
||||
cd "$HOMESICK/repos/rc-files" || return $?
|
||||
git config user.name "Homeshick user"
|
||||
git config user.email "homeshick@example.com"
|
||||
|
||||
cat >> home/.bashrc <<EOF
|
||||
#!/bin/bash
|
||||
PS1='\[33[01;32m\]\u@\h\[33[00m\]:\[33[01;34m\]\w\'
|
||||
homeshick --batch refresh
|
||||
EOF
|
||||
git add home/.bashrc
|
||||
git commit -m 'Added homeshick refresh check to .bashrc'
|
||||
}
|
||||
|
||||
@test 'check an up to date castle' {
|
||||
castle 'rc-files'
|
||||
$HOMESHICK_FN check rc-files
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" check rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;32m up to date${esc}0m rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'check an up to date castle with spaces in castle name' {
|
||||
castle 'repo with spaces in name'
|
||||
$HOMESHICK_FN check 'repo with spaces in name'
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" check "repo with spaces in name"
|
||||
expect -ex "${esc}1;36m checking${esc}0m repo with spaces in name\r${esc}1;32m up to date${esc}0m repo with spaces in name\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'check a castle that is behind upstream' {
|
||||
castle 'rc-files'
|
||||
(cd "$HOMESICK/repos/rc-files" && git reset --hard HEAD^1)
|
||||
run "$HOMESHICK_FN" check rc-files
|
||||
[ $status -eq 86 ] # EX_BEHIND
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" check rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;31m behind${esc}0m rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'check a castle that is behind upstream with a new file' {
|
||||
castle 'rc-files'
|
||||
(cd "$HOMESICK/repos/rc-files" && git reset --hard HEAD^ && add_new_file_to_castle)
|
||||
run "$HOMESHICK_FN" check rc-files
|
||||
[ $status -eq 86 ] # EX_BEHIND
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" check rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;31m behind${esc}0m rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'check a castle that is behind upstream with a modified file' {
|
||||
castle 'rc-files'
|
||||
(cd "$HOMESICK/repos/rc-files" && git reset --hard HEAD^1 && modify_file_in_castle)
|
||||
run "$HOMESHICK_FN" check rc-files
|
||||
[ $status -eq 86 ] # EX_BEHIND
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" check rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;31m behind${esc}0m rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'check a castle that is behind upstream with a deleted file' {
|
||||
castle 'rc-files'
|
||||
(cd "$HOMESICK/repos/rc-files" && git reset --hard HEAD^1 && delete_file_in_castle)
|
||||
run "$HOMESHICK_FN" check rc-files
|
||||
[ $status -eq 86 ] # EX_BEHIND
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" check rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;31m behind${esc}0m rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'check a castle that is ahead of upstream' {
|
||||
castle 'rc-files'
|
||||
(add_commit_to_castle)
|
||||
run "$HOMESHICK_FN" check rc-files
|
||||
[ $status -eq 85 ] # EX_AHEAD
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" check rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;31m ahead${esc}0m rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'check a castle that is ahead of upstream with a new file' {
|
||||
castle 'rc-files'
|
||||
(add_commit_to_castle && add_new_file_to_castle)
|
||||
run "$HOMESHICK_FN" check rc-files
|
||||
[ $status -eq 85 ] # EX_AHEAD
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" check rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;31m ahead${esc}0m rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'check a castle that is ahead of upstream with a modified file' {
|
||||
castle 'rc-files'
|
||||
(add_commit_to_castle; modify_file_in_castle)
|
||||
run "$HOMESHICK_FN" check rc-files
|
||||
[ $status -eq 85 ] # EX_AHEAD
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" check rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;31m ahead${esc}0m rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'check a castle that is ahead of upstream with a deleted file' {
|
||||
castle 'rc-files'
|
||||
(add_commit_to_castle && delete_file_in_castle)
|
||||
run "$HOMESHICK_FN" check rc-files
|
||||
[ $status -eq 85 ] # EX_AHEAD
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" check rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;31m ahead${esc}0m rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
|
||||
@test 'check a castle with a new file' {
|
||||
castle 'rc-files'
|
||||
(add_new_file_to_castle)
|
||||
run "$HOMESHICK_FN" check rc-files
|
||||
[ $status -eq 88 ] # EX_MODIFIED
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" check rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;31m modified${esc}0m rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'check a castle with a modified file' {
|
||||
castle 'rc-files'
|
||||
(modify_file_in_castle)
|
||||
run "$HOMESHICK_FN" check rc-files
|
||||
[ $status -eq 88 ] # EX_MODIFIED
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" check rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;31m modified${esc}0m rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'check a castle with a deleted file' {
|
||||
castle 'rc-files'
|
||||
(delete_file_in_castle)
|
||||
run "$HOMESHICK_FN" check rc-files
|
||||
[ $status -eq 88 ] # EX_MODIFIED
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" check rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;31m modified${esc}0m rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'check a castle with a new empty folder' {
|
||||
castle 'rc-files'
|
||||
(add_empty_folder_to_castle)
|
||||
$HOMESHICK_FN check rc-files
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" check rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;32m up to date${esc}0m rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'check a castle with a new folder and file in it' {
|
||||
castle 'rc-files'
|
||||
(
|
||||
add_empty_folder_to_castle && \
|
||||
cd "$HOMESICK/repos/rc-files" && \
|
||||
touch homeshick_new_folder_bats_test/some_file
|
||||
)
|
||||
run "$HOMESHICK_FN" check rc-files
|
||||
[ $status -eq 88 ] # EX_MODIFIED
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" check rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;31m modified${esc}0m rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
function setup() {
|
||||
setup_env
|
||||
source "$HOMESHICK_DIR/lib/fs.sh"
|
||||
}
|
||||
|
||||
function test_clean_path() {
|
||||
local path=$1
|
||||
local expected=$2
|
||||
local cleaned
|
||||
cleaned=$(clean_path "$path")
|
||||
if [[ $cleaned != "$expected" ]]; then
|
||||
printf "got '%s'\n" "$cleaned"
|
||||
[ "$expected" = "$cleaned" ]
|
||||
fi
|
||||
}
|
||||
|
||||
@test 'clean /home/user/somedir' {
|
||||
test_clean_path '/home/user/somedir' '/home/user/somedir'
|
||||
}
|
||||
|
||||
@test 'clean home/user/somedir' {
|
||||
test_clean_path 'home/user/somedir' 'home/user/somedir'
|
||||
}
|
||||
|
||||
@test 'clean /' {
|
||||
test_clean_path '/' '/'
|
||||
}
|
||||
|
||||
@test 'clean ""' {
|
||||
test_clean_path '' ''
|
||||
}
|
||||
|
||||
@test 'clean ../' {
|
||||
test_clean_path '../' '..'
|
||||
}
|
||||
|
||||
@test 'clean ../somedir' {
|
||||
test_clean_path '../somedir' '../somedir'
|
||||
}
|
||||
|
||||
@test 'clean /..' {
|
||||
test_clean_path '/..' '/'
|
||||
}
|
||||
|
||||
@test 'clean /../dir' {
|
||||
test_clean_path '/../dir' '/dir'
|
||||
}
|
||||
|
||||
@test 'clean somedir/../..' {
|
||||
test_clean_path 'somedir/../..' '..'
|
||||
}
|
||||
|
||||
@test 'clean /home/user/somedir/' {
|
||||
test_clean_path '/home/user/somedir/' '/home/user/somedir'
|
||||
}
|
||||
|
||||
@test 'clean /home/user/../user/somedir' {
|
||||
test_clean_path '/home/user/../user/somedir' '/home/user/somedir'
|
||||
}
|
||||
|
||||
@test 'clean /home/user/../user/somedir/' {
|
||||
test_clean_path '/home/user/../user/somedir/' '/home/user/somedir'
|
||||
}
|
||||
|
||||
@test 'clean /home/user/../../user/somedir/' {
|
||||
test_clean_path '/home/user/../../user/somedir/' '/user/somedir'
|
||||
}
|
||||
|
||||
@test 'clean /home/user/../anotherdir/../somedir/' {
|
||||
test_clean_path '/home/user/../anotherdir/../somedir/' '/home/somedir'
|
||||
}
|
||||
|
||||
@test 'clean /home/user/somedir/..' {
|
||||
test_clean_path '/home/user/somedir/..' '/home/user'
|
||||
}
|
||||
|
||||
@test 'clean /home/user/somedir/../' {
|
||||
test_clean_path '/home/user/somedir/../' '/home/user'
|
||||
}
|
||||
|
||||
@test 'clean /home/user/somedir/../../' {
|
||||
test_clean_path '/home/user/somedir/../../' '/home'
|
||||
}
|
||||
|
||||
@test 'clean /home/user/./somedir' {
|
||||
test_clean_path '/home/user/./somedir' '/home/user/somedir'
|
||||
}
|
||||
|
||||
@test 'clean /home/user/.' {
|
||||
test_clean_path '/home/user/.' '/home/user'
|
||||
}
|
||||
|
||||
@test 'clean /home/user/./' {
|
||||
test_clean_path '/home/user/./' '/home/user'
|
||||
}
|
||||
|
||||
@test 'clean /home/user/./somedir/../anotherdir/.' {
|
||||
test_clean_path '/home/user/./somedir/../anotherdir/.' '/home/user/anotherdir'
|
||||
}
|
||||
|
||||
@test 'clean /home/user../../somedir' {
|
||||
clean_path '/home/user../../somedir'
|
||||
test_clean_path '/home/user../../somedir' '/home/somedir'
|
||||
}
|
||||
|
||||
@test 'clean /home/user../../some dir' {
|
||||
clean_path '/home/user../../some dir'
|
||||
test_clean_path '/home/user../../some dir' '/home/some dir'
|
||||
}
|
||||
|
||||
@test 'clean /home/user name../../some dir' {
|
||||
clean_path '/home/user name../../some dir'
|
||||
test_clean_path '/home/user name../../some dir' '/home/some dir'
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
@test 'clone with github shorthand, while matching path exists' {
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
ping -c 1 -w 3 github.com || ping -c 1 -t 3 github.com || skip 'github not reachable'
|
||||
(cd "$HOME" && git init andsens/rc-files)
|
||||
open_bracket="\\u005b"
|
||||
close_bracket="\\u005d"
|
||||
esc="\\u001b$open_bracket"
|
||||
(cd "$HOME" && cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" --batch clone andsens/rc-files
|
||||
expect -ex "${esc}1;33m clone${esc}0m andsens/rc-files also exists as a filesystem path, use \`homeshick clone ./andsens/rc-files' to circumvent the github shorthand\r
|
||||
${esc}1;36m clone${esc}0m https://github.com/andsens/rc-files.git\r${esc}1;32m clone${esc}0m https://github.com/andsens/rc-files.git\r
|
||||
${esc}1;37m updates${esc}0m The castle rc-files has new files.\r
|
||||
${esc}1;36m symlink?${esc}0m BATCH - No\r${esc}1;31m symlink?${esc}0m BATCH - No\r
|
||||
" {} default {exit 1}
|
||||
expect EOF
|
||||
EOF
|
||||
)
|
||||
}
|
||||
|
||||
@test 'clone a repo' {
|
||||
fixture 'rc-files'
|
||||
$HOMESHICK_FN --batch clone "$REPO_FIXTURES/rc-files"
|
||||
}
|
||||
|
||||
@test 'clone a repo with spaces in name' {
|
||||
fixture 'repo with spaces in name'
|
||||
$HOMESHICK_FN --batch clone "$REPO_FIXTURES/repo with spaces in name"
|
||||
[ -d "$HOMESICK/repos/repo with spaces in name" ]
|
||||
}
|
||||
|
||||
@test 'prompt for symlinking after clone' {
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
|
||||
fixture 'rc-files'
|
||||
open_bracket="\\u005b"
|
||||
close_bracket="\\u005d"
|
||||
esc="\\u001b$open_bracket"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" clone "$REPO_FIXTURES/rc-files"
|
||||
expect -ex "${esc}1;36m clone${esc}0m $REPO_FIXTURES/rc-files\r${esc}1;32m clone${esc}0m $REPO_FIXTURES/rc-files\r
|
||||
${esc}1;37m updates${esc}0m The castle rc-files has new files.\r
|
||||
${esc}1;36m symlink?${esc}0m ${open_bracket}yN${close_bracket} " {} default {exit 1}
|
||||
send "y\n"
|
||||
expect EOF
|
||||
EOF
|
||||
[ -f "$HOME/.bashrc" ]
|
||||
}
|
||||
|
||||
@test 'clone repo with dot in its name' {
|
||||
fixture '135.abc'
|
||||
$HOMESHICK_FN --batch clone "$REPO_FIXTURES/135.abc"
|
||||
[ -e "$HOMESICK/repos/135.abc" ]
|
||||
}
|
||||
|
||||
@test 'recursive clone with git version >= 1.6.5' {
|
||||
fixture 'nested-submodules'
|
||||
GIT_VERSION=$(get_git_version)
|
||||
run version_compare "$GIT_VERSION" 1.6.5
|
||||
[[ $status == 2 ]] && skip 'git version too low'
|
||||
|
||||
$HOMESHICK_FN --batch clone "$REPO_FIXTURES/nested-submodules"
|
||||
[ -e "$HOMESICK/repos/nested-submodules/level1/level2" ]
|
||||
}
|
||||
|
||||
@test 'recursive clone with git version < 1.6.5' {
|
||||
fixture 'nested-submodules'
|
||||
|
||||
GIT_VERSION=1.6.4 $HOMESHICK_FN --batch clone "$REPO_FIXTURES/nested-submodules"
|
||||
[ -e "$HOMESICK/repos/nested-submodules/level1" ]
|
||||
[ ! -e "$HOMESICK/repos/nested-submodules/level1/level2/info" ]
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
function setup() {
|
||||
setup_env
|
||||
source "$HOMESHICK_DIR/lib/fs.sh"
|
||||
}
|
||||
|
||||
function test_rel_path() {
|
||||
local source_dir=$1
|
||||
local target=$2
|
||||
local expected=$3
|
||||
local link
|
||||
link=$(create_rel_path "$source_dir" "$target")
|
||||
if [[ $link != "$expected" ]]; then
|
||||
printf "got '%s'\n" "$link"
|
||||
[ "$expected" = "$cleaned" ]
|
||||
fi
|
||||
}
|
||||
|
||||
@test 'relpath from . to file' {
|
||||
touch "$HOME/file"
|
||||
test_rel_path "$HOME/" "$HOME/file" "file"
|
||||
}
|
||||
|
||||
@test 'relpath from folder/ to file' {
|
||||
mkdir "$HOME/folder"
|
||||
touch "$HOME/file"
|
||||
test_rel_path "$HOME/folder/" "$HOME/file" "../file"
|
||||
}
|
||||
|
||||
@test 'relpath from folder1/ to folder2/file' {
|
||||
mkdir "$HOME/folder1" "$HOME/folder2"
|
||||
touch "$HOME/folder2/file"
|
||||
test_rel_path "$HOME/folder1/" "$HOME/folder2/file" "../folder2/file"
|
||||
}
|
||||
|
||||
@test 'relpath from lvl1/lvl2/lvl3/ to lvl1-2/file' {
|
||||
mkdir -p "$HOME/lvl1/lvl2/lvl3" "$HOME/lvl1-2"
|
||||
touch "$HOME/lvl1-2/file"
|
||||
test_rel_path "$HOME/lvl1/lvl2/lvl3/" "$HOME/lvl1-2/file" "../../../lvl1-2/file"
|
||||
}
|
||||
|
||||
@test 'relpath from lvl1-2/ to lvl1/lvl2/lvl3/file' {
|
||||
mkdir -p "$HOME/lvl1/lvl2/lvl3" "$HOME/lvl1-2"
|
||||
touch "$HOME/lvl1/lvl2/lvl3/file"
|
||||
test_rel_path "$HOME/lvl1-2/" "$HOME/lvl1/lvl2/lvl3/file" "../lvl1/lvl2/lvl3/file"
|
||||
}
|
||||
|
||||
@test 'relpath from dir/ non-existent-file' {
|
||||
run create_rel_path "$HOME/dir" "$HOME/non-existent-file" "non-existent-file"
|
||||
}
|
||||
|
||||
@test 'relpath from dir/ dir2/non-existent-file' {
|
||||
run create_rel_path "$HOME/dir" "$HOME/dir2/non-existent-file" "dir2/non-existent-file"
|
||||
}
|
||||
|
||||
@test 'fail on non existent source_dir' {
|
||||
run create_rel_path "$HOME/dir/dir2" "$HOME/file"
|
||||
[ $status -eq 1 ]
|
||||
}
|
||||
|
||||
@test 'relpath from inside symlinked dir on same level as real dir to file' {
|
||||
mkdir -p "$HOME/realdir"
|
||||
ln -s "realdir" "$HOME/symlinkdir"
|
||||
touch "$HOME/file"
|
||||
test_rel_path "$HOME/symlinkdir/" "$HOME/file" "../file"
|
||||
}
|
||||
|
||||
@test 'relpath from inside symlinked dir on higher level than real dir to file' {
|
||||
mkdir -p "$HOME/somedir/realdir"
|
||||
ln -s "somedir/realdir" "$HOME/symlinkdir"
|
||||
touch "$HOME/file"
|
||||
test_rel_path "$HOME/symlinkdir/" "$HOME/file" "../../file"
|
||||
}
|
||||
|
||||
@test 'relpath from inside symlinked dir on higher level than real dir to file where root dir is symlinked dir' {
|
||||
mkdir "$HOME/root"
|
||||
ln -s "root" "$HOME/symlinked-root"
|
||||
mkdir -p "$HOME/symlinked-root/somedir/realdir"
|
||||
ln -s "somedir/realdir" "$HOME/symlinked-root/symlinkdir"
|
||||
touch "$HOME/symlinked-root/file"
|
||||
test_rel_path "$HOME/symlinked-root/symlinkdir/" "$HOME/symlinked-root/file" "../../file"
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
@test 'check non-existent and existent castle' {
|
||||
castle 'rc-files'
|
||||
run "$HOMESHICK_FN" check non-existent rc-files
|
||||
[ $status -eq 1 ] # EX_ERR
|
||||
}
|
||||
|
||||
@test 'clone non-existent and existent castle' {
|
||||
fixture 'rc-files'
|
||||
run "$HOMESHICK_FN" --batch clone "$REPO_FIXTURES/non-existent" "$REPO_FIXTURES/rc-files"
|
||||
[ $status -eq 70 ] # EX_SOFTWARE
|
||||
[ ! -d "$HOMESICK/repos/rc-files" ] # Should not exist, clone must fail early
|
||||
}
|
||||
|
||||
@test 'generate castles with and without naming conflict' {
|
||||
castle 'rc-files'
|
||||
run "$HOMESHICK_FN" generate rc-files nonexistent
|
||||
[ $status -eq 1 ] # EX_ERR
|
||||
[ ! -d "$HOMESICK/repos/nonexistent" ] # Should not exist, generate must fail early
|
||||
}
|
||||
|
||||
@test 'link non-existent and existent castle' {
|
||||
castle 'rc-files'
|
||||
run "$HOMESHICK_FN" link --batch rc-files nonexistent
|
||||
[ $status -eq 1 ] # EX_ERR
|
||||
}
|
||||
|
||||
@test 'pull non-existent and existent castle' {
|
||||
castle 'rc-files'
|
||||
local current_head
|
||||
current_head=$(cd "$HOMESICK/repos/rc-files" && git rev-parse HEAD)
|
||||
(cd "$HOMESICK/repos/rc-files" && git reset --hard HEAD^1)
|
||||
run "$HOMESHICK_FN" pull --batch non-existent rc-files
|
||||
[ $status -eq 1 ] # EX_ERR
|
||||
local pulled_head
|
||||
pulled_head=$(cd "$HOMESICK/repos/rc-files" && git rev-parse HEAD)
|
||||
[ "$current_head" = "$pulled_head" ]
|
||||
}
|
||||
|
||||
@test 'refresh non-existent and existent castle' {
|
||||
castle 'rc-files'
|
||||
$HOMESHICK_FN pull rc-files
|
||||
run "$HOMESHICK_FN" refresh --batch 7 non-existent rc-files
|
||||
[ $status -eq 1 ] # EX_ERR
|
||||
}
|
||||
|
||||
@test 'track non-existent and existent file in castle' {
|
||||
castle 'rc-files'
|
||||
touch "$HOME/.newfiletotrack"
|
||||
run "$HOMESHICK_FN" track rc-files "$HOME/.newfiletotrack" "$HOME/.nonexistent"
|
||||
[ $status -eq 1 ] # EX_ERR
|
||||
[ -f "$HOMESICK/repos/rc-files/home/.newfiletotrack" ]
|
||||
}
|
||||
|
||||
@test 'track existent and non-existent file in castle' {
|
||||
castle 'rc-files'
|
||||
touch "$HOME/.newfiletotrack"
|
||||
run "$HOMESHICK_FN" track rc-files non-existent .newfiletotrack
|
||||
[ $status -eq 1 ] # EX_ERR
|
||||
[ ! -f "$HOMESICK/repos/rc-files/home/.newfiletotrack" ]
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
@test 'generate a castle' {
|
||||
$HOMESHICK_FN --batch generate my_repo
|
||||
[ -d "$HOMESICK/repos/my_repo" ]
|
||||
}
|
||||
|
||||
@test 'generate a castle with spaces in name' {
|
||||
$HOMESHICK_FN --batch generate my\ repo
|
||||
[ -d "$HOMESICK/repos/my repo" ]
|
||||
}
|
||||
|
||||
@test 'generate a castle with spaces in name with fish' {
|
||||
[ "$(type -t fish)" = "file" ] || skip "fish not installed"
|
||||
cmd="source "$HOMESHICK_FN_SRC_FISH"; and $HOMESHICK_FN --batch generate my\ repo"
|
||||
fish <<< "$cmd" 2>&1
|
||||
[ -d "$HOMESICK/repos/my repo" ]
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
function setup() {
|
||||
setup_env
|
||||
source "$HOMESHICK_DIR/lib/commands/clone.sh"
|
||||
}
|
||||
|
||||
@test 'git url basename: git@... .git' {
|
||||
[ 'homeshick' = "$(repo_basename 'git@github.com:andsens/homeshick.git')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: https://... .git' {
|
||||
[ 'homeshick' = "$(repo_basename 'https://github.com/andsens/homeshick.git')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: filepath' {
|
||||
[ 'homeshick' = "$(repo_basename '/home/username/homeshick.git')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: git@... .git (dot in reponame)' {
|
||||
[ 'ebnf.vim' = "$(repo_basename 'git@github.com:vim-scripts/ebnf.vim.git')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: ssh://... .git (dot in reponame)' {
|
||||
[ 'ebnf.vim' = "$(repo_basename 'ssh://git@github.com/vim-scripts/ebnf.vim.git')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: http://... .git (dot in reponame)' {
|
||||
[ 'ebnf.vim' = "$(repo_basename 'https://github.com/vim-scripts/ebnf.vim.git')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: numbers in reponame' {
|
||||
[ 'spf13-vim' = "$(repo_basename 'https://github.com/vim-scripts/spf13/spf13-vim.git')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: git@... (no .git extension)' {
|
||||
[ 'homeshick' = "$(repo_basename 'git@github.com:andsens/homeshick')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: git@... (dot in reponame, no .git extension)' {
|
||||
[ 'ebnf.vim' = "$(repo_basename 'git@github.com:vim-scripts/ebnf.vim')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: filepath (no .git extension)' {
|
||||
[ 'homeshick' = "$(repo_basename '/home/username/homeshick')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: filepath (colon in reponame)' {
|
||||
[ 'dotfiles:emacs' = "$(repo_basename '/home/username/dotfiles:emacs.git')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: filepath (no extension, colon in reponame)' {
|
||||
[ 'dotfiles:emacs' = "$(repo_basename '/home/username/dotfiles:emacs.git')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: filepath (no extension, colon in reponame & path)' {
|
||||
[ 'dotfiles:emacs' = "$(repo_basename '/home/user:name/dotfiles:emacs')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: git@... (no extension, no subfolder)' {
|
||||
[ 'repo' = "$(repo_basename 'git@gitolite.example.com:repo')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: git@... (no extension, no subfolder, colon in reponame)' {
|
||||
[ 'dotfiles:emacs' = "$(repo_basename 'git@gitolite.example.com:dotfiles:emacs')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: ssh:// (no extension, no subfolder)' {
|
||||
[ 'repo' = "$(repo_basename 'ssh://git@gitolite.example.com/repo')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: ssh:// (no extension, no subfolder, colon in reponame)' {
|
||||
[ 'dotfiles:emacs' = "$(repo_basename 'ssh://git@gitolite.example.com/dotfiles:emacs')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: http://... (with portnumber)' {
|
||||
[ 'repo' = "$(repo_basename 'https://git.example.com:1234/repos/repo.git')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: http://... (with portnumber, colon in subfoldername)' {
|
||||
[ 'repo' = "$(repo_basename 'https://git.example.com:1234/dotfiles:emacs/repo.git')" ]
|
||||
}
|
||||
|
||||
@test 'git url basename: http://... (with portnumber, colon in reponame)' {
|
||||
[ 'repo:emacs' = "$(repo_basename 'https://git.example.com:1234/dotfiles/repo:emacs.git')" ]
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
@test 'bash with homeshick_dir override' {
|
||||
castle 'dotfiles'
|
||||
local result
|
||||
result=$( HOMESHICK_DIR=$_TMPDIR/nowhere "$HOMESHICK_FN" 2>&1 >/dev/null ) || true
|
||||
[[ "$result" =~ "/nowhere/" ]]
|
||||
}
|
||||
|
||||
@test 'fish with homeshick_dir override' {
|
||||
[ "$(type -t fish)" = "file" ] || skip "fish not installed"
|
||||
cmd="source "$HOMESHICK_FN_SRC_FISH"; set HOMESHICK_DIR \"$_TMPDIR/nowhere\"; $HOMESHICK_FN"
|
||||
local result=$( fish <<< "$cmd" 2>&1 >/dev/null )
|
||||
[[ "$result" =~ "/nowhere/" ]]
|
||||
}
|
||||
|
||||
@test 'csh with homeshick_dir override' {
|
||||
[ "$(type -t csh)" = "file" ] || skip "csh not installed"
|
||||
cmd="set HOMESHICK_DIR=/nowhere; source \"${HOMESHICK_FN_SRC_CSH}\""
|
||||
local result=$( csh <<< "$cmd" 2>&1 >/dev/null )
|
||||
[[ "$result" =~ "/nowhere/" ]]
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
@test 'link file with crazy name' {
|
||||
castle 'repo with spaces in name'
|
||||
$HOMESHICK_FN --batch link 'repo with spaces in name'
|
||||
stat "$HOME/.crazy
|
||||
file␇☺"
|
||||
test -f "$HOME/.crazy
|
||||
file␇☺"
|
||||
}
|
||||
|
||||
@test 'do not fail when linking file with newline' {
|
||||
castle 'rc-files'
|
||||
test_filename="filename
|
||||
newline"
|
||||
touch "$HOMESICK/repos/rc-files/home/$test_filename"
|
||||
commit_repo_state "$HOMESICK/repos/rc-files"
|
||||
$HOMESHICK_FN --batch link rc-files
|
||||
[ -L "$HOME/filename
|
||||
newline" ]
|
||||
is_symlink ".homesick/repos/rc-files/home/filename
|
||||
newline" "$HOME/filename
|
||||
newline"
|
||||
}
|
||||
|
||||
@test 'link a file with spaces in its name' {
|
||||
castle 'repo with spaces in name'
|
||||
$HOMESHICK_FN --batch link "repo with spaces in name"
|
||||
[ -f "$HOME/.file with spaces in name" ]
|
||||
[ -f "$HOME/.folder with spaces in name/another file with spaces in its name" ]
|
||||
}
|
||||
|
||||
@test 'only link submodule files inside home/' {
|
||||
castle 'submodule-outside-home'
|
||||
# '!' inverts the return value
|
||||
! $HOMESHICK_FN --batch link submodule-outside-home 2>&1 | grep 'No such file or directory'
|
||||
# This is the best I can do for testing.
|
||||
# The failure does not cause any files to be created
|
||||
# Ostensibly homeshick should exit with $? != 0 when linking fails, but it doesn't
|
||||
}
|
||||
|
||||
@test 'link files of nested submodules' {
|
||||
fixture 'nested-submodules'
|
||||
GIT_VERSION=$(get_git_version)
|
||||
run version_compare "$GIT_VERSION" 1.6.5
|
||||
[[ $status == 2 ]] && skip 'git version too low'
|
||||
|
||||
$HOMESHICK_FN --batch clone "$REPO_FIXTURES/nested-submodules"
|
||||
$HOMESHICK_FN --batch link nested-submodules
|
||||
[ -f "$HOME/.subdir1/.subdir2/.info2" ]
|
||||
}
|
||||
|
||||
@test "don't fail when linking uninitialized subrepos" {
|
||||
fixture 'nested-submodules'
|
||||
GIT_VERSION=$(get_git_version)
|
||||
run version_compare "$GIT_VERSION" 1.6.5
|
||||
[[ $status == 2 ]] && skip 'git version too low'
|
||||
|
||||
git clone "$REPO_FIXTURES/nested-submodules" "$HOMESICK/repos/nested-submodules"
|
||||
[ -f "$HOMESICK/repos/nested-submodules/info" ]
|
||||
$HOMESHICK_FN --batch link nested-submodules
|
||||
[ ! -f "$HOMESICK/repos/nested-submodules/home/.info" ]
|
||||
[ ! -f "$HOME/.info" ]
|
||||
}
|
||||
|
||||
@test 'link submodule files' {
|
||||
fixture 'nested-submodules'
|
||||
GIT_VERSION=$(get_git_version)
|
||||
run version_compare "$GIT_VERSION" 1.6.5
|
||||
[[ $status == 2 ]] && skip 'git version too low'
|
||||
|
||||
$HOMESHICK_FN --batch clone "$REPO_FIXTURES/nested-submodules"
|
||||
$HOMESHICK_FN --batch link nested-submodules
|
||||
[ -f "$HOME/.info" ]
|
||||
[ -f "$HOME/.subdir1/.info1" ]
|
||||
}
|
||||
|
||||
@test 'link repo with no dirs in home/' {
|
||||
castle 'nodirs'
|
||||
$HOMESHICK_FN --batch link nodirs
|
||||
[ -f "$HOME/.file1" ]
|
||||
}
|
||||
|
||||
@test 'create file-less parent directories' {
|
||||
castle 'dotfiles'
|
||||
$HOMESHICK_FN --batch link dotfiles
|
||||
[ -d "$HOME/.config/foo/bar" ]
|
||||
}
|
||||
|
||||
@test 'symlink to a relative symlink' {
|
||||
castle 'symlinks'
|
||||
echo "test" > "$HOME/file_in_homedir"
|
||||
$HOMESHICK_FN --batch link symlinks
|
||||
[ "$(cat "$HOME/link_to_homedir_file")" = 'test' ]
|
||||
}
|
||||
|
||||
@test 'overwrite prompt skipped when linking and --batch is on' {
|
||||
castle 'rc-files'
|
||||
touch "$HOME/.bashrc"
|
||||
$HOMESHICK_FN --batch link rc-files
|
||||
[ -f "$HOME/.bashrc" ]
|
||||
[ ! -L "$HOME/.bashrc" ]
|
||||
}
|
||||
|
||||
@test 'overwrite file with link when the prompt is answered with yes' {
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
|
||||
castle 'rc-files'
|
||||
open_bracket="\\u005b"
|
||||
close_bracket="\\u005d"
|
||||
esc="\\u001b$open_bracket"
|
||||
touch "$HOME/.bashrc"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" link rc-files
|
||||
expect -ex "${esc}1;37m conflict${esc}0m .bashrc exists\r
|
||||
${esc}1;36m overwrite?${esc}0m ${open_bracket}yN${close_bracket}" {} default {exit 1}
|
||||
send "y\n"
|
||||
expect EOF
|
||||
EOF
|
||||
[ -L "$HOME/.bashrc" ]
|
||||
}
|
||||
|
||||
@test "don't overwrite file or prompt for it when linking and --skip is on" {
|
||||
castle 'rc-files'
|
||||
touch "$HOME/.bashrc"
|
||||
$HOMESHICK_FN --skip link rc-files
|
||||
[ -f "$HOME/.bashrc" -a ! -L "$HOME/.bashrc" ]
|
||||
}
|
||||
|
||||
@test 'existing symlinks are not relinked when running link' {
|
||||
castle 'module-files'
|
||||
$HOMESHICK_FN --batch link module-files
|
||||
local inode_before
|
||||
inode_before=$(get_inode_no "$HOME/.my_module")
|
||||
$HOMESHICK_FN --batch link module-files
|
||||
local inode_after
|
||||
inode_after=$(get_inode_no "$HOME/.my_module")
|
||||
[ "$inode_before" -eq "$inode_after" ]
|
||||
}
|
||||
|
||||
@test 'traverse into the folder structure when linking' {
|
||||
castle 'dotfiles'
|
||||
mkdir -p "$HOME/.config/bar.dir"
|
||||
cat > "$HOME/.config/foo.conf" <<EOF
|
||||
#I am just a regular foo.conf file
|
||||
[foo]
|
||||
A=True
|
||||
EOF
|
||||
cat > "$HOME/.config/bar.dir/bar.conf" <<EOF
|
||||
#I am just a regular bar.conf file
|
||||
[bar]
|
||||
A=True
|
||||
EOF
|
||||
|
||||
[ -f "$HOME/.config/foo.conf" ]
|
||||
#.config/foo.conf should be overwritten by a directory of the same name
|
||||
[ -d "$HOME/.config/bar.dir" ]
|
||||
#.config/bar.dir should be overwritten by a file of the same name
|
||||
$HOMESHICK_FN --batch --force link dotfiles
|
||||
[ -d "$HOME/.config/foo.conf" ]
|
||||
[ -f "$HOME/.config/bar.dir" ]
|
||||
}
|
||||
|
||||
@test 'treat symlinked directories in the castle like files when linking' {
|
||||
castle 'module-files'
|
||||
$HOMESHICK_FN --batch link module-files
|
||||
[ -L "$HOME/.my_module" ]
|
||||
}
|
||||
|
||||
@test '.git directories are not symlinked' {
|
||||
castle 'dotfiles'
|
||||
$HOMESHICK_FN --batch link dotfiles
|
||||
[ ! -e "$HOME/.vim/.git" ]
|
||||
}
|
||||
|
||||
@test 'link a castle with spaces in its name' {
|
||||
castle 'repo with spaces in name'
|
||||
$HOMESHICK_FN --batch link repo\ with\ spaces\ in\ name
|
||||
[ -f "$HOME/.repowithspacesfile" ]
|
||||
}
|
||||
|
||||
@test 'pass multiple castlenames to link' {
|
||||
castle 'rc-files'
|
||||
castle 'dotfiles'
|
||||
castle 'repo with spaces in name'
|
||||
$HOMESHICK_FN --batch link rc-files dotfiles repo\ with\ spaces\ in\ name
|
||||
is_symlink .homesick/repos/rc-files/home/.bashrc "$HOME/.bashrc"
|
||||
is_symlink ../.homesick/repos/dotfiles/home/.ssh/known_hosts "$HOME/.ssh/known_hosts"
|
||||
is_symlink ".homesick/repos/repo with spaces in name/home/.repowithspacesfile" "$HOME/.repowithspacesfile"
|
||||
}
|
||||
|
||||
@test 'link all castles when no castle is specified' {
|
||||
castle 'rc-files'
|
||||
castle 'dotfiles'
|
||||
castle 'repo with spaces in name'
|
||||
$HOMESHICK_FN --batch link
|
||||
is_symlink .homesick/repos/rc-files/home/.bashrc "$HOME/.bashrc"
|
||||
is_symlink ../.homesick/repos/dotfiles/home/.ssh/known_hosts "$HOME/.ssh/known_hosts"
|
||||
is_symlink ".homesick/repos/repo with spaces in name/home/.repowithspacesfile" "$HOME/.repowithspacesfile"
|
||||
}
|
||||
|
||||
@test 'files ignored by git should not be linked' {
|
||||
castle 'dotfiles'
|
||||
touch "$HOMESICK/repos/dotfiles/home/shouldBeIgnored.txt"
|
||||
cat > "$HOMESICK/repos/dotfiles/.gitignore" <<EOF
|
||||
shouldBeIgnored.txt
|
||||
EOF
|
||||
commit_repo_state "$HOMESICK/repos/dotfiles"
|
||||
$HOMESHICK_FN --batch link dotfiles
|
||||
[ ! -L "$HOME/shouldBeIgnored.txt" ]
|
||||
}
|
||||
|
||||
@test 'link file into directory that is a relative symlink' {
|
||||
castle 'dotfiles'
|
||||
mkdir -p "$HOME/two-levels/under-home"
|
||||
ln -s "two-levels/under-home" "$HOME/.ssh"
|
||||
$HOMESHICK_FN --batch link
|
||||
is_symlink ../../.homesick/repos/dotfiles/home/.ssh/known_hosts "$HOME/two-levels/under-home/known_hosts"
|
||||
[ -f "$HOME/.ssh/known_hosts" ]
|
||||
}
|
||||
|
||||
@test 'link file into directory that is an absolute symlink' {
|
||||
castle 'dotfiles'
|
||||
mkdir -p "$HOME/two-levels/under-home"
|
||||
ln -s "$HOME/two-levels/under-home" "$HOME/.config"
|
||||
$HOMESHICK_FN --batch link
|
||||
is_symlink ../../.homesick/repos/dotfiles/home/.config/bar.dir "$HOME/two-levels/under-home/bar.dir"
|
||||
}
|
|
@ -0,0 +1,259 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
## This is the linking table we are trying to verify:
|
||||
## "not directory" can be a regular file or a symlink to either a file or a directory
|
||||
## $HOME\repo | not directory | directory ##
|
||||
## ---------------------|---------------|---------- ##
|
||||
## nonexistent | link | mkdir ##
|
||||
## symlink to repofile | identical | rm!&mkdir ##
|
||||
## file | rm?&link | rm?&mkdir ##
|
||||
## directory | rm?&link | identical ##
|
||||
## directory (symlink) | rm?&link | identical ##
|
||||
|
||||
|
||||
## First row: nonexistent
|
||||
## First column: not directory
|
||||
@test 'link file to nonexistent' {
|
||||
castle 'rc-files'
|
||||
$HOMESHICK_FN --batch link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/.bashrc "$HOME/.bashrc"
|
||||
}
|
||||
|
||||
@test 'link file symlink to nonexistent' {
|
||||
castle 'rc-files'
|
||||
$HOMESHICK_FN --batch link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/symlinked-file "$HOME/symlinked-file"
|
||||
}
|
||||
|
||||
@test 'link dir symlink to nonexistent' {
|
||||
castle 'rc-files'
|
||||
$HOMESHICK_FN --batch link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/symlinked-directory "$HOME/symlinked-directory"
|
||||
}
|
||||
|
||||
@test 'link dead symlink to nonexistent' {
|
||||
castle 'rc-files'
|
||||
$HOMESHICK_FN --batch link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/dead-symlink "$HOME/dead-symlink"
|
||||
}
|
||||
|
||||
## First row: nonexistent
|
||||
## Second column: directory
|
||||
@test 'link dir to nonexistent' {
|
||||
castle 'dotfiles'
|
||||
$HOMESHICK_FN --batch link dotfiles
|
||||
[ ! -L "$HOME/.ssh" ]
|
||||
[ -d "$HOME/.ssh" ]
|
||||
}
|
||||
|
||||
|
||||
## Second row: symlink to repofile
|
||||
## First column: not directory
|
||||
@test 'link file to reposymlink' {
|
||||
castle 'rc-files'
|
||||
$HOMESHICK_FN --batch link rc-files
|
||||
local inode_before
|
||||
inode_before=$(get_inode_no "$HOME/.bashrc")
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
local inode_after
|
||||
inode_after=$(get_inode_no "$HOME/.bashrc")
|
||||
is_symlink .homesick/repos/rc-files/home/.bashrc "$HOME/.bashrc"
|
||||
[ "$inode_before" -eq "$inode_after" ]
|
||||
}
|
||||
|
||||
@test 'link file symlink to reposymlink' {
|
||||
castle 'rc-files'
|
||||
$HOMESHICK_FN --batch link rc-files
|
||||
local inode_before
|
||||
inode_before=$(get_inode_no "$HOME/symlinked-file")
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
local inode_after
|
||||
inode_after=$(get_inode_no "$HOME/symlinked-file")
|
||||
is_symlink .homesick/repos/rc-files/home/symlinked-file "$HOME/symlinked-file"
|
||||
[ "$inode_before" -eq "$inode_after" ]
|
||||
}
|
||||
|
||||
@test 'link dir symlink to reposymlink' {
|
||||
castle 'rc-files'
|
||||
$HOMESHICK_FN --batch link rc-files
|
||||
local inode_before
|
||||
inode_before=$(get_inode_no "$HOME/symlinked-directory")
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
local inode_after
|
||||
inode_after=$(get_inode_no "$HOME/symlinked-directory")
|
||||
is_symlink .homesick/repos/rc-files/home/symlinked-directory "$HOME/symlinked-directory"
|
||||
[ "$inode_before" -eq "$inode_after" ]
|
||||
}
|
||||
|
||||
@test 'link dead symlink to reposymlink' {
|
||||
castle 'rc-files'
|
||||
$HOMESHICK_FN --batch link rc-files
|
||||
local inode_before
|
||||
inode_before=$(get_inode_no "$HOME/dead-symlink")
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
local inode_after
|
||||
inode_after=$(get_inode_no "$HOME/dead-symlink")
|
||||
is_symlink .homesick/repos/rc-files/home/dead-symlink "$HOME/dead-symlink"
|
||||
[ "$inode_before" -eq "$inode_after" ]
|
||||
}
|
||||
|
||||
## Second row: symlink to repofile
|
||||
## Second column: directory
|
||||
@test 'link legacy symlinks' {
|
||||
castle 'dotfiles'
|
||||
# Recreate the legacy scenario
|
||||
ln -s "$HOMESICK/repos/dotfiles/home/.ssh" "$HOME/.ssh"
|
||||
$HOMESHICK_FN --batch --force link dotfiles
|
||||
# Without legacy handling if we were to run `file $HOME/.ssh/known_hosts` we would get
|
||||
# .ssh/known_hosts: symbolic link in a loop
|
||||
# The `test -e` is sufficient though
|
||||
[ -e "$HOME/.ssh/known_hosts" ]
|
||||
}
|
||||
|
||||
|
||||
## Third row: file
|
||||
## First column: not directory
|
||||
@test 'link file to file' {
|
||||
castle 'rc-files'
|
||||
touch "$HOME/.bashrc"
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/.bashrc "$HOME/.bashrc"
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/.bashrc "$HOME/.bashrc"
|
||||
}
|
||||
|
||||
@test 'link file symlink to file' {
|
||||
castle 'rc-files'
|
||||
touch "$HOME/symlinked-file"
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/symlinked-file "$HOME/symlinked-file"
|
||||
}
|
||||
|
||||
@test 'link dir symlink to file' {
|
||||
castle 'rc-files'
|
||||
mkdir "$HOME/symlinked-directory"
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/symlinked-directory "$HOME/symlinked-directory"
|
||||
}
|
||||
|
||||
@test 'link dead symlink to file' {
|
||||
castle 'rc-files'
|
||||
touch "$HOME/dead-symlink"
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/dead-symlink "$HOME/dead-symlink"
|
||||
}
|
||||
|
||||
## Third row: file
|
||||
## Second column: directory
|
||||
@test 'link dir to file' {
|
||||
castle 'dotfiles'
|
||||
touch "$HOME/.ssh"
|
||||
$HOMESHICK_FN --batch --force link dotfiles
|
||||
[ -d "$HOME/.ssh" ]
|
||||
[ ! -L "$HOME/.ssh" ]
|
||||
}
|
||||
|
||||
|
||||
## Fourth row: directory
|
||||
## First column: not directory
|
||||
@test 'link file to dir' {
|
||||
castle 'rc-files'
|
||||
mkdir "$HOME/.bashrc"
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/.bashrc "$HOME/.bashrc"
|
||||
}
|
||||
|
||||
@test 'link file symlink to dir' {
|
||||
castle 'rc-files'
|
||||
mkdir "$HOME/symlinked-file"
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/symlinked-file "$HOME/symlinked-file"
|
||||
}
|
||||
|
||||
@test 'link dir symlink to dir' {
|
||||
castle 'rc-files'
|
||||
mkdir "$HOME/symlinked-directory"
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/symlinked-directory "$HOME/symlinked-directory"
|
||||
}
|
||||
|
||||
@test 'link dead symlink to dir' {
|
||||
castle 'rc-files'
|
||||
mkdir "$HOME/dead-symlink"
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/dead-symlink "$HOME/dead-symlink"
|
||||
}
|
||||
|
||||
## Fourth row: directory
|
||||
## Second column: directory
|
||||
@test 'link dir to dir' {
|
||||
castle 'dotfiles'
|
||||
mkdir "$HOME/.ssh"
|
||||
local inode_before
|
||||
inode_before=$(get_inode_no "$HOME/.ssh")
|
||||
$HOMESHICK_FN --batch --force link dotfiles
|
||||
local inode_after
|
||||
inode_after=$(get_inode_no "$HOME/.ssh")
|
||||
[ "$inode_before" -eq "$inode_after" ]
|
||||
[ -d "$HOME/.ssh" ]
|
||||
[ ! -L "$HOME/.ssh" ]
|
||||
}
|
||||
|
||||
|
||||
## Fourth row: directory
|
||||
## First column: not directory
|
||||
@test 'link file to dir symlink' {
|
||||
castle 'rc-files'
|
||||
mkdir "$NOTHOME/symlink-target-dir"
|
||||
ln -s "$NOTHOME/symlink-target-dir" "$HOME/.bashrc"
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/.bashrc "$HOME/.bashrc"
|
||||
rm -rf "$NOTHOME/symlink-target-dir"
|
||||
}
|
||||
|
||||
@test 'link file symlink to dir symlink' {
|
||||
castle 'rc-files'
|
||||
mkdir "$NOTHOME/symlink-target-dir"
|
||||
ln -s "$NOTHOME/symlink-target-dir" "$HOME/symlinked-file"
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/symlinked-file "$HOME/symlinked-file"
|
||||
rm -rf "$NOTHOME/symlink-target-dir"
|
||||
}
|
||||
|
||||
@test 'link dir symlink to dir symlink' {
|
||||
castle 'rc-files'
|
||||
mkdir "$NOTHOME/symlink-target-dir"
|
||||
ln -s "$NOTHOME/symlink-target-dir" "$HOME/symlinked-directory"
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/symlinked-directory "$HOME/symlinked-directory"
|
||||
rm -rf "$NOTHOME/symlink-target-dir"
|
||||
}
|
||||
|
||||
@test 'link dead symlink to dir symlink' {
|
||||
castle 'rc-files'
|
||||
mkdir "$NOTHOME/symlink-target-dir"
|
||||
ln -s "$NOTHOME/symlink-target-dir" "$HOME/dead-symlink"
|
||||
$HOMESHICK_FN --batch --force link rc-files
|
||||
is_symlink .homesick/repos/rc-files/home/dead-symlink "$HOME/dead-symlink"
|
||||
rm -rf "$NOTHOME/symlink-target-dir"
|
||||
}
|
||||
|
||||
## Fourth row: directory
|
||||
## Second column: directory
|
||||
@test 'link dir to dir symlink' {
|
||||
castle 'dotfiles'
|
||||
mkdir "$NOTHOME/symlink-target-dir"
|
||||
ln -s "$NOTHOME/symlink-target-dir" "$HOME/.ssh"
|
||||
local inode_before
|
||||
inode_before=$(get_inode_no "$HOME/.ssh")
|
||||
$HOMESHICK_FN --batch --force link dotfiles
|
||||
local inode_after
|
||||
inode_after=$(get_inode_no "$HOME/.ssh")
|
||||
is_symlink "$NOTHOME/symlink-target-dir" "$HOME/.ssh"
|
||||
[ "$inode_before" -eq "$inode_after" ]
|
||||
[ -d "$HOME/.ssh" ]
|
||||
[ -L "$HOME/.ssh" ]
|
||||
rm -rf "$NOTHOME/symlink-target-dir"
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
@test 'list all castles' {
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
|
||||
castle 'rc-files'
|
||||
castle 'dotfiles'
|
||||
castle 'module-files'
|
||||
castle 'repo with spaces in name'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" list
|
||||
expect -ex "${esc}1;37m dotfiles${esc}0m $REPO_FIXTURES/dotfiles\r
|
||||
${esc}1;37m module-files${esc}0m $REPO_FIXTURES/module-files\r
|
||||
${esc}1;37m rc-files${esc}0m $REPO_FIXTURES/rc-files\r
|
||||
${esc}1;37mrepo with spaces in name${esc}0m $REPO_FIXTURES/repo with spaces in name\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'list castle with spaces in castlename' {
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
|
||||
castle 'repo with spaces in name'
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" list
|
||||
expect -ex "${esc}1;37mrepo with spaces in name${esc}0m $REPO_FIXTURES/repo with spaces in name\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'list castle with altered upstream remote name' {
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
|
||||
castle 'rc-files'
|
||||
(cd "$HOMESICK/repos/rc-files" && git remote rename origin nigiro)
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" list
|
||||
expect -ex "${esc}1;37m rc-files${esc}0m $REPO_FIXTURES/rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'list castle with slash in branchname' {
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
|
||||
castle 'rc-files'
|
||||
(cd "$HOMESICK/repos/rc-files" && git checkout branch/with/slash)
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" list
|
||||
expect -ex "${esc}1;37m rc-files${esc}0m $REPO_FIXTURES/rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'list symlinks to castles' {
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
castle 'rc-files'
|
||||
(
|
||||
cd "$HOMESICK/repos/" && \
|
||||
mv rc-files "$_TMPDIR/rc-files" && \
|
||||
ln -s "$_TMPDIR/rc-files" ./rc-files
|
||||
)
|
||||
esc="\\u001b\\u005b"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" list
|
||||
expect -ex "${esc}1;37m rc-files${esc}0m $REPO_FIXTURES/rc-files\r\n" {} default {exit 1}
|
||||
EOF
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
@test 'invoke non existing command' {
|
||||
run "$HOMESHICK_FN" commandthatdoesnexist
|
||||
[ $status -eq 64 ] # EX_USAGE
|
||||
}
|
||||
|
||||
@test 'verbose mode should print identical messages when linking' {
|
||||
castle 'symlinks'
|
||||
$HOMESHICK_FN link symlinks
|
||||
$HOMESHICK_FN -v link symlinks | grep identical
|
||||
}
|
||||
|
||||
@test 'normal verbosity should not print identical messages when linking' {
|
||||
castle 'symlinks'
|
||||
$HOMESHICK_FN link symlinks
|
||||
[ ! $($HOMESHICK_FN link symlinks | grep identical) ]
|
||||
}
|
||||
|
||||
@test 'link non-existent castle' {
|
||||
run "$HOMESHICK_FN" link nonexistent
|
||||
[ $status -eq 1 ] # EX_ERR
|
||||
}
|
||||
|
||||
@test 'error should end with a single newline' {
|
||||
$HOMESHICK_FN --batch generate existing-repo
|
||||
output=$($HOMESHICK_FN --batch generate existing-repo 2>&1 | tr '\n' 'n')
|
||||
run grep -q 'nn$' <<<"$output"
|
||||
[ $status -eq 1 ]
|
||||
}
|
||||
|
||||
@test 'fish function should not print errors when invoked without arguments' {
|
||||
[ "$(type -t fish)" = "file" ] || skip "fish not installed"
|
||||
cmd="source "$HOMESHICK_FN_SRC_FISH"; and $HOMESHICK_FN"
|
||||
local stderr
|
||||
stderr=$( fish <<< "$cmd" 2>&1 >/dev/null )
|
||||
[ -z "$stderr" ]
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
@test 'pull skips castles with no upstream remote' {
|
||||
castle 'rc-files'
|
||||
castle 'dotfiles'
|
||||
# The dotfiles FETCH_HEAD should not exist after cloning
|
||||
[ ! -e "$HOMESICK/repos/dotfiles/.git/FETCH_HEAD" ]
|
||||
(cd "$HOMESICK/repos/rc-files" && git remote rm origin)
|
||||
run "$HOMESHICK_FN" pull rc-files dotfiles
|
||||
[ $status -eq 0 ] # EX_SUCCESS
|
||||
# dotfiles FETCH_HEAD should exist if the castle was pulled
|
||||
[ -e "$HOMESICK/repos/dotfiles/.git/FETCH_HEAD" ]
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
@test 'refresh a freshly cloned castle' {
|
||||
castle 'rc-files'
|
||||
run "$HOMESHICK_FN" refresh -b rc-files
|
||||
[ $status -eq 87 ] # EX_TH_EXCEEDED
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
open_bracket="\\u005b"
|
||||
close_bracket="\\u005d"
|
||||
esc="\\u001b$open_bracket"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" refresh rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;31m outdated${esc}0m rc-files\r
|
||||
${esc}1;37m refresh${esc}0m The castle rc-files is outdated.\r
|
||||
${esc}1;36m pull?${esc}0m ${open_bracket}yN${close_bracket} " {} default {exit 1}
|
||||
send "y\n"
|
||||
expect EOF
|
||||
EOF
|
||||
}
|
||||
|
||||
@test 'refresh a castle that was just pulled' {
|
||||
castle 'rc-files'
|
||||
$HOMESHICK_FN pull rc-files
|
||||
$HOMESHICK_FN -b refresh 7 rc-files
|
||||
}
|
||||
|
||||
@test 'refresh a castle that was pulled 8 days ago' {
|
||||
castle 'rc-files'
|
||||
$HOMESHICK_FN pull rc-files # creates the FETCH_HEAD file
|
||||
|
||||
local fetch_head
|
||||
local timestamp
|
||||
fetch_head="$HOMESICK/repos/rc-files/.git/FETCH_HEAD"
|
||||
system=`uname -a`
|
||||
if [[ "$system" =~ "Linux" ]]; then
|
||||
timestamp=$(date -d@$(($(date +%s)-6*86400)) '+%Y%m%d%H%M.%S')
|
||||
else
|
||||
# assume BSD system
|
||||
timestamp=$(date -r $(($(date +%s)-6*86400)) '+%Y%m%d%H%M.%S')
|
||||
fi
|
||||
touch -t "$timestamp" "$fetch_head"
|
||||
run "$HOMESHICK_FN" refresh -b rc-files
|
||||
[ $status -eq 87 ] # EX_TH_EXCEEDED
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
open_bracket="\\u005b"
|
||||
close_bracket="\\u005d"
|
||||
esc="\\u001b$open_bracket"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" refresh rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;31m outdated${esc}0m rc-files\r
|
||||
${esc}1;37m refresh${esc}0m The castle rc-files is outdated.\r
|
||||
${esc}1;36m pull?${esc}0m ${open_bracket}yN${close_bracket} " {} default {exit 1}
|
||||
send "y\n"
|
||||
expect EOF
|
||||
EOF
|
||||
}
|
||||
|
||||
|
||||
@test 'refresh a castle and check that it is up to date' {
|
||||
castle 'rc-files'
|
||||
|
||||
local current_head
|
||||
current_head=$(cd "$HOMESICK/repos/rc-files" && git rev-parse HEAD)
|
||||
(cd "$HOMESICK/repos/rc-files" && git reset --hard HEAD^1)
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
open_bracket="\\u005b"
|
||||
close_bracket="\\u005d"
|
||||
esc="\\u001b$open_bracket"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" refresh rc-files
|
||||
expect -ex "${esc}1;36m checking${esc}0m rc-files\r${esc}1;31m outdated${esc}0m rc-files\r
|
||||
${esc}1;37m refresh${esc}0m The castle rc-files is outdated.\r
|
||||
${esc}1;36m pull?${esc}0m ${open_bracket}yN${close_bracket} " {} default {exit 1}
|
||||
send "y\n"
|
||||
expect EOF
|
||||
EOF
|
||||
local pulled_head
|
||||
pulled_head=$(cd "$HOMESICK/repos/rc-files" && git rev-parse HEAD)
|
||||
[ "$current_head" = "$pulled_head" ]
|
||||
}
|
||||
|
||||
@test 'refresh a castle with spaces in name' {
|
||||
castle 'repo with spaces in name'
|
||||
run "$HOMESHICK_FN" refresh -b 'repo with spaces in name'
|
||||
[ $status -eq 87 ] # EX_TH_EXCEEDED
|
||||
|
||||
$EXPECT_INSTALLED || skip 'expect not installed'
|
||||
open_bracket="\\u005b"
|
||||
close_bracket="\\u005d"
|
||||
esc="\\u001b$open_bracket"
|
||||
cat <<EOF | expect -f -
|
||||
spawn "$HOMESHICK_BIN" refresh "repo with spaces in name"
|
||||
expect -ex "${esc}1;36m checking${esc}0m repo with spaces in name\r${esc}1;31m outdated${esc}0m repo with spaces in name\r
|
||||
${esc}1;37m refresh${esc}0m The castle repo with spaces in name is outdated.\r
|
||||
${esc}1;36m pull?${esc}0m ${open_bracket}yN${close_bracket} " {} default {exit 1}
|
||||
send "y\n"
|
||||
expect EOF
|
||||
EOF
|
||||
}
|
|
@ -0,0 +1,319 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load ../helper
|
||||
|
||||
@test 'track non-existent file' {
|
||||
castle 'symlinks'
|
||||
run "$HOMESHICK_FN" track symlinks "$HOME/non-existent-file"
|
||||
[ $status -eq 1 ] # EX_ERR
|
||||
}
|
||||
|
||||
@test 'track relative symlink in $HOME' {
|
||||
castle 'symlinks'
|
||||
echo "test" > "$HOME/some_file"
|
||||
(cd "$HOME" && ln -s some_file link_to_some_file)
|
||||
$HOMESHICK_FN track symlinks "$HOME/link_to_some_file"
|
||||
[ "$(cat "$HOME/link_to_some_file")" = 'test' ]
|
||||
is_symlink ../../../../some_file "$HOMESICK/repos/symlinks/home/link_to_some_file"
|
||||
}
|
||||
|
||||
@test 'track absolute symlink' {
|
||||
castle 'symlinks'
|
||||
echo "test" > "$HOME/some_file"
|
||||
(cd "$HOME" && ln -s "$HOME/some_file" link_to_some_file)
|
||||
$HOMESHICK_FN track symlinks "$HOME/link_to_some_file"
|
||||
[ "$(cat "$HOME/link_to_some_file")" = 'test' ]
|
||||
is_symlink "$HOME/some_file" "$HOMESICK/repos/symlinks/home/link_to_some_file"
|
||||
}
|
||||
|
||||
@test 'track relative symlink in deep folder structure to file outside $HOME' {
|
||||
castle 'symlinks'
|
||||
mkdir -p "$NOTHOME/some/folder/outside/home"
|
||||
echo "test" > "$NOTHOME/some/folder/outside/home/some_file"
|
||||
mkdir -p "$HOME/somedir/someotherdir/deep"
|
||||
(cd "$HOME" && ln -s ../../../../nothome/some/folder/outside/home/some_file "$HOME/somedir/someotherdir/deep/link_to_some_file")
|
||||
$HOMESHICK_FN track symlinks "$HOME/somedir/someotherdir/deep/link_to_some_file"
|
||||
[ "$(cat "$HOME/somedir/someotherdir/deep/link_to_some_file")" = 'test' ]
|
||||
local relpath='../../../../../../../../nothome/some/folder/outside/home/some_file'
|
||||
is_symlink "$relpath" "$HOMESICK/repos/symlinks/home/somedir/someotherdir/deep/link_to_some_file"
|
||||
}
|
||||
|
||||
@test 'track relative snakelike symlink' {
|
||||
castle 'symlinks'
|
||||
mkdir -p "$HOME/some/folder"
|
||||
mkdir -p "$HOME/someother/folder"
|
||||
echo "test" > "$HOME/someother/folder/some_file"
|
||||
mkdir -p "$HOME/somethird/folder"
|
||||
(cd "$HOME" && ln -s ../../some/folder/../../someother/folder/some_file "$HOME/somethird/folder/link_to_some_file")
|
||||
$HOMESHICK_FN track symlinks "$HOME/somethird/folder/link_to_some_file"
|
||||
[ "$(cat "$HOME/somethird/folder/link_to_some_file")" = 'test' ]
|
||||
local relpath='../../../../../../someother/folder/some_file'
|
||||
is_symlink "$relpath" "$HOMESICK/repos/symlinks/home/somethird/folder/link_to_some_file"
|
||||
}
|
||||
|
||||
@test 'track dead symlink' {
|
||||
castle 'symlinks'
|
||||
(cd "$HOME" && ln -s some_file link_to_some_file)
|
||||
$HOMESHICK_FN track symlinks "$HOME/link_to_some_file"
|
||||
[ ! -e "$HOME/link_to_some_file" ]
|
||||
is_symlink ../../../../some_file "$HOMESICK/repos/symlinks/home/link_to_some_file"
|
||||
}
|
||||
|
||||
@test 'track absolute path' {
|
||||
castle 'rc-files'
|
||||
cat > "$HOME/.zshrc" <<EOF
|
||||
homeshick --batch refresh
|
||||
EOF
|
||||
$HOMESHICK_FN track rc-files "$HOME/.zshrc"
|
||||
[ -f "$HOMESICK/repos/rc-files/home/.zshrc" ]
|
||||
[ -L "$HOME/.zshrc" ]
|
||||
}
|
||||
|
||||
@test 'track path with spaces' {
|
||||
castle 'rc-files'
|
||||
cat > "$HOME/.path with spaces" <<EOF
|
||||
homeshick --batch refresh
|
||||
EOF
|
||||
$HOMESHICK_FN track rc-files "$HOME/.path with spaces"
|
||||
[ -f "$HOMESICK/repos/rc-files/home/.path with spaces" ]
|
||||
[ -L "$HOME/.path with spaces" ]
|
||||
}
|
||||
|
||||
@test 'track path with spaces (spaces in foldername and filename)' {
|
||||
castle 'rc-files'
|
||||
mkdir -p "$HOME/deep folder/structure/with spaces"
|
||||
local file="$HOME/deep folder/structure/with spaces/.file with spaces"
|
||||
cat > "$file" <<EOF
|
||||
homeshick --batch refresh
|
||||
EOF
|
||||
$HOMESHICK_FN track rc-files "$HOME/deep folder/structure/with spaces/.file with spaces"
|
||||
[ -f "$HOMESICK/repos/rc-files/home/deep folder/structure/with spaces/.file with spaces" ]
|
||||
[ -L "$file" ]
|
||||
}
|
||||
|
||||
@test 'track two paths with spaces' {
|
||||
castle 'rc-files'
|
||||
mkdir -p "$HOME/deep folder/structure/with spaces"
|
||||
local file1="$HOME/deep folder/structure/with spaces/.file with spaces"
|
||||
local file2="$HOME/.path with spaces"
|
||||
cat > "$file1" <<EOF
|
||||
homeshick --batch refresh
|
||||
EOF
|
||||
cat > "$file2" <<EOF
|
||||
homeshick --batch refresh
|
||||
EOF
|
||||
$HOMESHICK_FN track rc-files "$HOME/.path with spaces" "$HOME/deep folder/structure/with spaces/.file with spaces"
|
||||
[ -f "$HOMESICK/repos/rc-files/home/deep folder/structure/with spaces/.file with spaces" ]
|
||||
[ -L "$file1" ]
|
||||
[ -f "$HOMESICK/repos/rc-files/home/.path with spaces" ]
|
||||
[ -L "$file2" ]
|
||||
}
|
||||
|
||||
@test 'track relative path' {
|
||||
castle 'rc-files'
|
||||
cat > "$HOME/.zshrc" <<EOF
|
||||
homeshick --batch refresh
|
||||
EOF
|
||||
(cd "$HOME" && $HOMESHICK_FN track rc-files .zshrc)
|
||||
[ -f "$HOMESICK/repos/rc-files/home/.zshrc" ]
|
||||
[ -L "$HOME/.zshrc" ]
|
||||
}
|
||||
|
||||
@test 'track in castle with spaces' {
|
||||
castle 'repo with spaces in name'
|
||||
cat > "$HOME/.vimrc" <<EOF
|
||||
My empty vim config
|
||||
EOF
|
||||
(cd "$HOME" && $HOMESHICK_FN track repo\ with\ spaces\ in\ name .vimrc)
|
||||
local file="$HOMESICK/repos/repo with spaces in name/home/.vimrc"
|
||||
[ -f "$file" ]
|
||||
[ -L "$HOME/.vimrc" ]
|
||||
}
|
||||
|
||||
@test 'disallow tracking outside homedir' {
|
||||
castle 'rc-files'
|
||||
cat > "$NOTHOME/some_other_file" <<EOF
|
||||
homeshick should refuse to track this file
|
||||
EOF
|
||||
run "$HOMESHICK_FN" track rc-files "$NOTHOME/some_other_file"
|
||||
[ $status -eq 1 ]
|
||||
[ -e "$NOTHOME/some_other_file" ]
|
||||
[ ! -L "$NOTHOME/some_other_file" ]
|
||||
rm "$NOTHOME/some_other_file"
|
||||
}
|
||||
|
||||
@test 'disallow overwrite when tracking' {
|
||||
castle 'rc-files'
|
||||
cat > "$HOME/.zshrc" <<EOF
|
||||
homeshick --batch refresh
|
||||
EOF
|
||||
$HOMESHICK_FN track rc-files "$HOME/.zshrc"
|
||||
[ -L "$HOME/.zshrc" ]
|
||||
rm "$HOME/.zshrc"
|
||||
cat > "$HOME/.zshrc" <<EOF
|
||||
homeshick --batch refresh 7
|
||||
EOF
|
||||
$HOMESHICK_FN track rc-files "$HOME/.zshrc"
|
||||
local tracked_file_size
|
||||
tracked_file_size=$(stat -c %s "$HOMESICK/repos/rc-files/home/.zshrc" 2>/dev/null || \
|
||||
stat -f %z "$HOMESICK/repos/rc-files/home/.zshrc")
|
||||
[ 26 -eq "$tracked_file_size" ]
|
||||
[ ! -L "$HOME/.zshrc" ]
|
||||
}
|
||||
|
||||
@test 'disallow double tracking' {
|
||||
castle 'rc-files'
|
||||
cat > "$HOME/.zshrc" <<EOF
|
||||
homeshick --batch refresh
|
||||
EOF
|
||||
$HOMESHICK_FN track rc-files "$HOME/.zshrc"
|
||||
[ -L "$HOME/.zshrc" ]
|
||||
$HOMESHICK_FN track rc-files "$HOME/.zshrc"
|
||||
[ ! -L "$HOMESICK/repos/rc-files/home/.zshrc" ]
|
||||
}
|
||||
|
||||
@test 'git add when tracked' {
|
||||
castle 'rc-files'
|
||||
cat > "$HOME/.zshrc" <<EOF
|
||||
homeshick --batch refresh
|
||||
EOF
|
||||
$HOMESHICK_FN track rc-files "$HOME/.zshrc"
|
||||
local git_status
|
||||
git_status=$(cd "$HOMESICK/repos/rc-files" && git status --porcelain)
|
||||
[ "A home/.zshrc" = "$git_status" ]
|
||||
}
|
||||
|
||||
@test 'track folder' {
|
||||
castle 'rc-files'
|
||||
mkdir -p "$HOME/.somefolder/subfolder/stuff"
|
||||
touch "$HOME/.somefolder/file1"
|
||||
touch "$HOME/.somefolder/subfolder/file2"
|
||||
touch "$HOME/.somefolder/subfolder/file3"
|
||||
touch "$HOME/.somefolder/subfolder/stuff/file4"
|
||||
$HOMESHICK_FN track rc-files "$HOME/.somefolder"
|
||||
[ -e "$HOMESICK/repos/rc-files/home/.somefolder/file1" ]
|
||||
[ -e "$HOMESICK/repos/rc-files/home/.somefolder/subfolder/file2" ]
|
||||
[ -e "$HOMESICK/repos/rc-files/home/.somefolder/subfolder/file3" ]
|
||||
[ -e "$HOMESICK/repos/rc-files/home/.somefolder/subfolder/stuff/file4" ]
|
||||
}
|
||||
|
||||
@test "don't track ignored file" {
|
||||
castle 'rc-files'
|
||||
mkdir "$HOME/.folder"
|
||||
touch "$HOME/.folder/somefile.swp"
|
||||
$HOMESHICK_FN track rc-files "$HOME/.folder/somefile.swp"
|
||||
[ ! -e "$HOMESICK/repos/rc-files/home/.folder/somefile.swp" ]
|
||||
}
|
||||
|
||||
@test "don't track ignored files in folder" {
|
||||
castle 'rc-files'
|
||||
mkdir "$HOME/.folder"
|
||||
touch "$HOME/.folder/somefile.swp"
|
||||
touch "$HOME/.folder/trackthisthough"
|
||||
$HOMESHICK_FN track rc-files "$HOME/.folder/"
|
||||
[ ! -e "$HOMESICK/repos/rc-files/home/.folder/somefile.swp" ]
|
||||
[ -e "$HOMESICK/repos/rc-files/home/.folder/trackthisthough" ]
|
||||
}
|
||||
|
||||
@test 'track folder with spaces in name' {
|
||||
castle 'rc-files'
|
||||
mkdir -p "$HOME/.some folder/sub folder/stuff"
|
||||
touch "$HOME/.some folder/file"
|
||||
touch "$HOME/.some folder/sub folder/stuff/other file"
|
||||
$HOMESHICK_FN track rc-files "$HOME/.some folder"
|
||||
[ -e "$HOMESICK/repos/rc-files/home/.some folder/file" ]
|
||||
[ -e "$HOMESICK/repos/rc-files/home/.some folder/sub folder/stuff/other file" ]
|
||||
}
|
||||
|
||||
@test 'track with globbing' {
|
||||
castle 'rc-files'
|
||||
mkdir -p "$HOME/.folder/subfolder" "$HOME/.folder/subfolder2"
|
||||
touch "$HOME/.folder/ignored.swp"
|
||||
touch "$HOME/.folder/notglobbed.bash"
|
||||
touch "$HOME/.folder/globbed2.exclude"
|
||||
touch "$HOME/.folder/subfolder/this_as_well.bash"
|
||||
touch "$HOME/.folder/subfolder2/and_this.bash"
|
||||
$HOMESHICK_FN track rc-files "$HOME/.folder"/**/*.bash
|
||||
[ ! -e "$HOMESICK/repos/rc-files/home/.folder/ignored.swp" ]
|
||||
[ ! -e "$HOMESICK/repos/rc-files/home/.folder/notglobbed.bash" ]
|
||||
[ ! -e "$HOMESICK/repos/rc-files/home/.folder/globbed2.exclude" ]
|
||||
[ -e "$HOMESICK/repos/rc-files/home/.folder/subfolder/this_as_well.bash" ]
|
||||
[ -e "$HOMESICK/repos/rc-files/home/.folder/subfolder2/and_this.bash" ]
|
||||
}
|
||||
|
||||
@test 'track file in new folder with git version >= 1.8.2' {
|
||||
GIT_VERSION=$(git --version | grep 'git version' | cut -d ' ' -f 3)
|
||||
[[ ! $GIT_VERSION =~ ([0-9]+)(\.[0-9]+){0,3} ]] && skip 'could not detect git version'
|
||||
run version_compare "$GIT_VERSION" 1.8.2
|
||||
[[ $status == 2 ]] && skip 'git version too low'
|
||||
|
||||
castle 'rc-files'
|
||||
mkdir "$HOME/.folder"
|
||||
touch "$HOME/.folder/ignored.swp"
|
||||
$HOMESHICK_FN track rc-files "$HOME/.folder/ignored.swp"
|
||||
[ ! -e "$HOMESICK/repos/rc-files/home/.folder/ignored.swp" ]
|
||||
[ ! -e "$HOMESICK/repos/rc-files/home/.folder" ]
|
||||
}
|
||||
|
||||
@test 'track file in new folder with mocked git version < 1.8.2' {
|
||||
# git > 2.3 exits with $?=1 when trying to track an ignored file.
|
||||
# In lower versions this was 128, so the homeshick fallback check
|
||||
# will not work.
|
||||
# This is not a problem, it simply means that we can not emulate
|
||||
# git < 1.8.2 behavior with git > 2.3.0 and must skip the test.
|
||||
GIT_VERSION=$(git --version | grep 'git version' | cut -d ' ' -f 3)
|
||||
[[ ! $GIT_VERSION =~ ([0-9]+)(\.[0-9]+){0,3} ]] && skip 'could not detect git version'
|
||||
run version_compare "$GIT_VERSION" 2.3.0
|
||||
[[ $status -lt 2 ]] && skip 'git version too high'
|
||||
|
||||
castle 'rc-files'
|
||||
|
||||
mkdir "$HOME/.folder"
|
||||
touch "$HOME/.folder/ignored.swp"
|
||||
GIT_VERSION=1.8.0 $HOMESHICK_FN track rc-files "$HOME/.folder/ignored.swp"
|
||||
[ ! -e "$HOMESICK/repos/rc-files/home/.folder/ignored.swp" ]
|
||||
[ -e "$HOMESICK/repos/rc-files/home/.folder" ]
|
||||
}
|
||||
|
||||
@test 'track symlink in $HOME to $HOME' {
|
||||
castle 'symlinks'
|
||||
ln -s . .home
|
||||
(cd "$HOME" && ln -s . .home)
|
||||
$HOMESHICK_FN track symlinks "$HOME/.home"
|
||||
is_symlink ../../../.. "$HOMESICK/repos/symlinks/home/.home"
|
||||
}
|
||||
|
||||
@test 'track file pointing at hidden dir outside home' {
|
||||
castle 'symlinks'
|
||||
mkdir "$NOTHOME/..some"
|
||||
ln -s ../nothome/..some/file "$HOME/.test"
|
||||
$HOMESHICK_FN track symlinks "$HOME/.test"
|
||||
is_symlink ../../../../../nothome/..some/file "$HOMESICK/repos/symlinks/home/.test"
|
||||
}
|
||||
|
||||
@test 'track file pointing at hidden dir in snakelike fashion' {
|
||||
castle 'symlinks'
|
||||
ln -s .some/../.file "$HOME/.test"
|
||||
$HOMESHICK_FN track symlinks "$HOME/.test"
|
||||
is_symlink ../../../../.file "$HOMESICK/repos/symlinks/home/.test"
|
||||
}
|
||||
|
||||
@test 'track regular file from a dir that is a symlink' {
|
||||
castle 'symlinks'
|
||||
mkdir -p "$HOME/two-levels/under-home"
|
||||
ln -s "two-levels/under-home" "$HOME/symlinked-dir"
|
||||
touch "$HOME/symlinked-dir/trackme"
|
||||
$HOMESHICK_FN track symlinks "$HOME/symlinked-dir/trackme"
|
||||
is_symlink ../../.homesick/repos/symlinks/home/symlinked-dir/trackme "$HOME/symlinked-dir/trackme"
|
||||
}
|
||||
|
||||
@test 'track relative symlink from a dir that is a symlink' {
|
||||
castle 'symlinks'
|
||||
mkdir -p "$HOME/two-levels/under-home"
|
||||
ln -s "two-levels/under-home" "$HOME/symlinked-dir"
|
||||
touch "$HOME/linktome"
|
||||
ln -s "../linktome" "$HOME/symlinked-dir/trackme"
|
||||
$HOMESHICK_FN track symlinks "$HOME/symlinked-dir/trackme"
|
||||
is_symlink ../../../../../linktome "$HOMESICK/repos/symlinks/home/symlinked-dir/trackme"
|
||||
is_symlink ../../.homesick/repos/symlinks/home/symlinked-dir/trackme "$HOME/symlinked-dir/trackme"
|
||||
}
|
Loading…
Reference in New Issue