Merge branch 'upstream_master'
This commit is contained in:
commit
58160cb1f3
|
@ -6,10 +6,11 @@ python:
|
||||||
- "3.5"
|
- "3.5"
|
||||||
- "3.6"
|
- "3.6"
|
||||||
- "3.7"
|
- "3.7"
|
||||||
|
- "3.8"
|
||||||
- "nightly"
|
- "nightly"
|
||||||
- "pypy3"
|
- "pypy3"
|
||||||
|
|
||||||
sudo: false
|
sudo: false
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- ./test/test_travis
|
- ./test/test
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
**Copyright (c) 2014-2019 Anish Athalye (me@anishathalye.com)**
|
**Copyright (c) 2014-2020 Anish Athalye (me@anishathalye.com)**
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
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
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|
14
README.md
14
README.md
|
@ -1,4 +1,4 @@
|
||||||
# Dotbot [![Build Status](https://travis-ci.org/anishathalye/dotbot.svg?branch=master)](https://travis-ci.org/anishathalye/dotbot)
|
# Dotbot [![Build Status](https://travis-ci.com/anishathalye/dotbot.svg?branch=master)](https://travis-ci.com/anishathalye/dotbot)
|
||||||
|
|
||||||
Dotbot makes installing your dotfiles as easy as `git clone $url && cd dotfiles
|
Dotbot makes installing your dotfiles as easy as `git clone $url && cd dotfiles
|
||||||
&& ./install`, even on a freshly installed system!
|
&& ./install`, even on a freshly installed system!
|
||||||
|
@ -176,8 +176,10 @@ Available extended configuration parameters:
|
||||||
| `relink` | Removes the old target if it's a symlink (default:false) |
|
| `relink` | Removes the old target if it's a symlink (default:false) |
|
||||||
| `force` | Force removes the old target, file or folder, and forces a new link (default:false) |
|
| `force` | Force removes the old target, file or folder, and forces a new link (default:false) |
|
||||||
| `relative` | Use a relative path to the source when creating the symlink (default:false, absolute links) |
|
| `relative` | Use a relative path to the source when creating the symlink (default:false, absolute links) |
|
||||||
|
| `canonicalize-path` | Resolve any symbolic links encountered in the source to symlink to the canonical path (default:true, real paths) |
|
||||||
| `glob` | Treat a `*` character as a wildcard, and perform link operations on all of those matches (default:false) |
|
| `glob` | Treat a `*` character as a wildcard, and perform link operations on all of those matches (default:false) |
|
||||||
| `if` | Execute this in your `$SHELL` and only link if it is successful. |
|
| `if` | Execute this in your `$SHELL` and only link if it is successful. |
|
||||||
|
| `ignore-missing` | Do not fail if the source is missing and create the link anyway (default:false) |
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
|
@ -302,7 +304,9 @@ Clean commands are specified as an array of directories to be cleaned.
|
||||||
Clean commands support an extended configuration syntax. In this type of
|
Clean commands support an extended configuration syntax. In this type of
|
||||||
configuration, commands are specified as directory paths mapping to options. If
|
configuration, commands are specified as directory paths mapping to options. If
|
||||||
the `force` option is set to `true`, dead links are removed even if they don't
|
the `force` option is set to `true`, dead links are removed even if they don't
|
||||||
point to a file inside the dotfiles directory.
|
point to a file inside the dotfiles directory. If `recursive` is set to `true`,
|
||||||
|
the directory is traversed recursively (not recommended for `~` because it will
|
||||||
|
be slow).
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
|
@ -310,8 +314,10 @@ point to a file inside the dotfiles directory.
|
||||||
- clean: ['~']
|
- clean: ['~']
|
||||||
|
|
||||||
- clean:
|
- clean:
|
||||||
~/.config:
|
~/:
|
||||||
force: true
|
force: true
|
||||||
|
~/.config:
|
||||||
|
recursive: true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Defaults
|
### Defaults
|
||||||
|
@ -395,7 +401,7 @@ Do you have a feature request, bug report, or patch? Great! See
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (c) 2014-2019 Anish Athalye. Released under the MIT License. See
|
Copyright (c) 2014-2020 Anish Athalye. Released under the MIT License. See
|
||||||
[LICENSE.md][license] for details.
|
[LICENSE.md][license] for details.
|
||||||
|
|
||||||
[PyPI]: https://pypi.org/project/dotbot/
|
[PyPI]: https://pypi.org/project/dotbot/
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from .cli import main
|
from .cli import main
|
||||||
from .plugin import Plugin
|
from .plugin import Plugin
|
||||||
|
|
||||||
__version__ = '1.16.0'
|
__version__ = '1.17.0'
|
||||||
|
|
|
@ -73,10 +73,10 @@ def main():
|
||||||
if not isinstance(tasks, list):
|
if not isinstance(tasks, list):
|
||||||
raise ReadingError('Configuration file must be a list of tasks')
|
raise ReadingError('Configuration file must be a list of tasks')
|
||||||
if options.base_directory:
|
if options.base_directory:
|
||||||
base_directory = options.base_directory
|
base_directory = os.path.abspath(options.base_directory)
|
||||||
else:
|
else:
|
||||||
# default to directory of config file
|
# default to directory of config file
|
||||||
base_directory = os.path.dirname(os.path.realpath(options.config_file))
|
base_directory = os.path.dirname(os.path.abspath(options.config_file))
|
||||||
os.chdir(base_directory)
|
os.chdir(base_directory)
|
||||||
dispatcher = Dispatcher(base_directory)
|
dispatcher = Dispatcher(base_directory)
|
||||||
success = dispatcher.dispatch(tasks)
|
success = dispatcher.dispatch(tasks)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import copy
|
import copy
|
||||||
|
import os
|
||||||
|
|
||||||
class Context(object):
|
class Context(object):
|
||||||
'''
|
'''
|
||||||
|
@ -13,8 +14,11 @@ class Context(object):
|
||||||
def set_base_directory(self, base_directory):
|
def set_base_directory(self, base_directory):
|
||||||
self._base_directory = base_directory
|
self._base_directory = base_directory
|
||||||
|
|
||||||
def base_directory(self):
|
def base_directory(self, canonical_path=True):
|
||||||
return self._base_directory
|
base_directory = self._base_directory
|
||||||
|
if canonical_path:
|
||||||
|
base_directory = os.path.realpath(base_directory)
|
||||||
|
return base_directory
|
||||||
|
|
||||||
def set_defaults(self, defaults):
|
def set_defaults(self, defaults):
|
||||||
self._defaults = defaults
|
self._defaults = defaults
|
||||||
|
|
|
@ -10,8 +10,8 @@ class Dispatcher(object):
|
||||||
self._load_plugins()
|
self._load_plugins()
|
||||||
|
|
||||||
def _setup_context(self, base_directory):
|
def _setup_context(self, base_directory):
|
||||||
path = os.path.abspath(os.path.realpath(
|
path = os.path.abspath(
|
||||||
os.path.expanduser(base_directory)))
|
os.path.expanduser(base_directory))
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
raise DispatchError('Nonexistent base directory')
|
raise DispatchError('Nonexistent base directory')
|
||||||
self._context = Context(path)
|
self._context = Context(path)
|
||||||
|
|
|
@ -18,18 +18,20 @@ class Clean(dotbot.Plugin):
|
||||||
def _process_clean(self, targets):
|
def _process_clean(self, targets):
|
||||||
success = True
|
success = True
|
||||||
defaults = self._context.defaults().get(self._directive, {})
|
defaults = self._context.defaults().get(self._directive, {})
|
||||||
force = defaults.get('force', False)
|
|
||||||
for target in targets:
|
for target in targets:
|
||||||
if isinstance(targets, dict):
|
force = defaults.get('force', False)
|
||||||
|
recursive = defaults.get('recursive', False)
|
||||||
|
if isinstance(targets, dict) and isinstance(targets[target], dict):
|
||||||
force = targets[target].get('force', force)
|
force = targets[target].get('force', force)
|
||||||
success &= self._clean(target, force)
|
recursive = targets[target].get('recursive', recursive)
|
||||||
|
success &= self._clean(target, force, recursive)
|
||||||
if success:
|
if success:
|
||||||
self._log.info('All targets have been cleaned')
|
self._log.info('All targets have been cleaned')
|
||||||
else:
|
else:
|
||||||
self._log.error('Some targets were not successfully cleaned')
|
self._log.error('Some targets were not successfully cleaned')
|
||||||
return success
|
return success
|
||||||
|
|
||||||
def _clean(self, target, force):
|
def _clean(self, target, force, recursive):
|
||||||
'''
|
'''
|
||||||
Cleans all the broken symbolic links in target if they point to
|
Cleans all the broken symbolic links in target if they point to
|
||||||
a subdirectory of the base directory or if forced to clean.
|
a subdirectory of the base directory or if forced to clean.
|
||||||
|
@ -39,6 +41,11 @@ class Clean(dotbot.Plugin):
|
||||||
return True
|
return True
|
||||||
for item in os.listdir(os.path.expandvars(os.path.expanduser(target))):
|
for item in os.listdir(os.path.expandvars(os.path.expanduser(target))):
|
||||||
path = os.path.join(os.path.expandvars(os.path.expanduser(target)), item)
|
path = os.path.join(os.path.expandvars(os.path.expanduser(target)), item)
|
||||||
|
if recursive and os.path.isdir(path):
|
||||||
|
# isdir implies not islink -- we don't want to descend into
|
||||||
|
# symlinked directories. okay to do a recursive call here
|
||||||
|
# because depth should be fairly limited
|
||||||
|
self._clean(path, force, recursive)
|
||||||
if not os.path.exists(path) and os.path.islink(path):
|
if not os.path.exists(path) and os.path.islink(path):
|
||||||
points_at = os.path.join(os.path.dirname(path), os.readlink(path))
|
points_at = os.path.join(os.path.dirname(path), os.readlink(path))
|
||||||
if self._in_directory(path, self._context.base_directory()) or force:
|
if self._in_directory(path, self._context.base_directory()) or force:
|
||||||
|
|
|
@ -26,19 +26,23 @@ class Link(dotbot.Plugin):
|
||||||
for destination, source in links.items():
|
for destination, source in links.items():
|
||||||
destination = os.path.expandvars(destination)
|
destination = os.path.expandvars(destination)
|
||||||
relative = defaults.get('relative', False)
|
relative = defaults.get('relative', False)
|
||||||
|
canonical_path = defaults.get('canonicalize-path', True)
|
||||||
force = defaults.get('force', False)
|
force = defaults.get('force', False)
|
||||||
relink = defaults.get('relink', False)
|
relink = defaults.get('relink', False)
|
||||||
create = defaults.get('create', False)
|
create = defaults.get('create', False)
|
||||||
use_glob = defaults.get('glob', False)
|
use_glob = defaults.get('glob', False)
|
||||||
test = defaults.get('if', None)
|
test = defaults.get('if', None)
|
||||||
|
ignore_missing = defaults.get('ignore-missing', False)
|
||||||
if isinstance(source, dict):
|
if isinstance(source, dict):
|
||||||
# extended config
|
# extended config
|
||||||
test = source.get('if', test)
|
test = source.get('if', test)
|
||||||
relative = source.get('relative', relative)
|
relative = source.get('relative', relative)
|
||||||
|
canonical_path = source.get('canonicalize-path', canonical_path)
|
||||||
force = source.get('force', force)
|
force = source.get('force', force)
|
||||||
relink = source.get('relink', relink)
|
relink = source.get('relink', relink)
|
||||||
create = source.get('create', create)
|
create = source.get('create', create)
|
||||||
use_glob = source.get('glob', use_glob)
|
use_glob = source.get('glob', use_glob)
|
||||||
|
ignore_missing = source.get('ignore-missing', ignore_missing)
|
||||||
path = self._default_source(destination, source.get('path'))
|
path = self._default_source(destination, source.get('path'))
|
||||||
else:
|
else:
|
||||||
path = self._default_source(destination, source)
|
path = self._default_source(destination, source)
|
||||||
|
@ -49,25 +53,25 @@ class Link(dotbot.Plugin):
|
||||||
if use_glob:
|
if use_glob:
|
||||||
self._log.debug("Globbing with path: " + str(path))
|
self._log.debug("Globbing with path: " + str(path))
|
||||||
glob_results = glob.glob(path)
|
glob_results = glob.glob(path)
|
||||||
if len(glob_results) is 0:
|
if len(glob_results) == 0:
|
||||||
self._log.warning("Globbing couldn't find anything matching " + str(path))
|
self._log.warning("Globbing couldn't find anything matching " + str(path))
|
||||||
success = False
|
success = False
|
||||||
continue
|
continue
|
||||||
glob_star_loc = path.find('*')
|
glob_star_loc = path.find('*')
|
||||||
if glob_star_loc is -1 and destination[-1] is '/':
|
if glob_star_loc == -1 and destination[-1] == '/':
|
||||||
self._log.error("Ambiguous action requested.")
|
self._log.error("Ambiguous action requested.")
|
||||||
self._log.error("No wildcard in glob, directory use undefined: " +
|
self._log.error("No wildcard in glob, directory use undefined: " +
|
||||||
destination + " -> " + str(glob_results))
|
destination + " -> " + str(glob_results))
|
||||||
self._log.warning("Did you want to link the directory or into it?")
|
self._log.warning("Did you want to link the directory or into it?")
|
||||||
success = False
|
success = False
|
||||||
continue
|
continue
|
||||||
elif glob_star_loc is -1 and len(glob_results) is 1:
|
elif glob_star_loc == -1 and len(glob_results) == 1:
|
||||||
# perform a normal link operation
|
# perform a normal link operation
|
||||||
if create:
|
if create:
|
||||||
success &= self._create(destination)
|
success &= self._create(destination)
|
||||||
if force or relink:
|
if force or relink:
|
||||||
success &= self._delete(path, destination, relative, force)
|
success &= self._delete(path, destination, relative, canonical_path, force)
|
||||||
success &= self._link(path, destination, relative)
|
success &= self._link(path, destination, relative, canonical_path, ignore_missing)
|
||||||
else:
|
else:
|
||||||
self._log.lowinfo("Globs from '" + path + "': " + str(glob_results))
|
self._log.lowinfo("Globs from '" + path + "': " + str(glob_results))
|
||||||
glob_base = path[:glob_star_loc]
|
glob_base = path[:glob_star_loc]
|
||||||
|
@ -77,19 +81,23 @@ class Link(dotbot.Plugin):
|
||||||
if create:
|
if create:
|
||||||
success &= self._create(glob_link_destination)
|
success &= self._create(glob_link_destination)
|
||||||
if force or relink:
|
if force or relink:
|
||||||
success &= self._delete(glob_full_item, glob_link_destination, relative, force)
|
success &= self._delete(glob_full_item, glob_link_destination, relative, canonical_path, force)
|
||||||
success &= self._link(glob_full_item, glob_link_destination, relative)
|
success &= self._link(glob_full_item, glob_link_destination, relative, canonical_path, ignore_missing)
|
||||||
else:
|
else:
|
||||||
if create:
|
if create:
|
||||||
success &= self._create(destination)
|
success &= self._create(destination)
|
||||||
if not self._exists(os.path.join(self._context.base_directory(), path)):
|
if not ignore_missing and not self._exists(os.path.join(self._context.base_directory(), path)):
|
||||||
|
# we seemingly check this twice (here and in _link) because
|
||||||
|
# if the file doesn't exist and force is True, we don't
|
||||||
|
# want to remove the original (this is tested by
|
||||||
|
# link-force-leaves-when-nonexistent.bash)
|
||||||
success = False
|
success = False
|
||||||
self._log.warning('Nonexistent source %s -> %s' %
|
self._log.warning('Nonexistent source %s -> %s' %
|
||||||
(destination, path))
|
(destination, path))
|
||||||
continue
|
continue
|
||||||
if force or relink:
|
if force or relink:
|
||||||
success &= self._delete(path, destination, relative, force)
|
success &= self._delete(path, destination, relative, canonical_path, force)
|
||||||
success &= self._link(path, destination, relative)
|
success &= self._link(path, destination, relative, canonical_path, ignore_missing)
|
||||||
if success:
|
if success:
|
||||||
self._log.info('All links have been set up')
|
self._log.info('All links have been set up')
|
||||||
else:
|
else:
|
||||||
|
@ -153,9 +161,9 @@ class Link(dotbot.Plugin):
|
||||||
self._log.lowinfo('Creating directory %s' % parent)
|
self._log.lowinfo('Creating directory %s' % parent)
|
||||||
return success
|
return success
|
||||||
|
|
||||||
def _delete(self, source, path, relative, force):
|
def _delete(self, source, path, relative, canonical_path, force):
|
||||||
success = True
|
success = True
|
||||||
source = os.path.join(self._context.base_directory(), source)
|
source = os.path.join(self._context.base_directory(canonical_path=canonical_path), source)
|
||||||
fullpath = os.path.expanduser(path)
|
fullpath = os.path.expanduser(path)
|
||||||
if relative:
|
if relative:
|
||||||
source = self._relative_path(source, fullpath)
|
source = self._relative_path(source, fullpath)
|
||||||
|
@ -189,7 +197,7 @@ class Link(dotbot.Plugin):
|
||||||
destination_dir = os.path.dirname(destination)
|
destination_dir = os.path.dirname(destination)
|
||||||
return os.path.relpath(source, destination_dir)
|
return os.path.relpath(source, destination_dir)
|
||||||
|
|
||||||
def _link(self, source, link_name, relative):
|
def _link(self, source, link_name, relative, canonical_path, ignore_missing):
|
||||||
'''
|
'''
|
||||||
Links link_name to source.
|
Links link_name to source.
|
||||||
|
|
||||||
|
@ -197,7 +205,8 @@ class Link(dotbot.Plugin):
|
||||||
'''
|
'''
|
||||||
success = False
|
success = False
|
||||||
destination = os.path.expanduser(link_name)
|
destination = os.path.expanduser(link_name)
|
||||||
absolute_source = os.path.join(self._context.base_directory(), source)
|
base_directory = self._context.base_directory(canonical_path=canonical_path)
|
||||||
|
absolute_source = os.path.join(base_directory, source)
|
||||||
if relative:
|
if relative:
|
||||||
source = self._relative_path(absolute_source, destination)
|
source = self._relative_path(absolute_source, destination)
|
||||||
else:
|
else:
|
||||||
|
@ -209,7 +218,7 @@ class Link(dotbot.Plugin):
|
||||||
# we need to use absolute_source below because our cwd is the dotfiles
|
# we need to use absolute_source below because our cwd is the dotfiles
|
||||||
# directory, and if source is relative, it will be relative to the
|
# directory, and if source is relative, it will be relative to the
|
||||||
# destination directory
|
# destination directory
|
||||||
elif not self._exists(link_name) and self._exists(absolute_source):
|
elif not self._exists(link_name) and (ignore_missing or self._exists(absolute_source)):
|
||||||
try:
|
try:
|
||||||
os.symlink(source, destination)
|
os.symlink(source, destination)
|
||||||
except OSError:
|
except OSError:
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 0f64cbfa54b0b22dc7b776b7b98a7cd657e84d78
|
Subproject commit 2f463cf5b0e98a52bc20e348d1e69761bf263b86
|
2
setup.py
2
setup.py
|
@ -73,7 +73,7 @@ setup(
|
||||||
],
|
],
|
||||||
|
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'PyYAML>=5.1.2,<6',
|
'PyYAML>=5.3,<6',
|
||||||
],
|
],
|
||||||
|
|
||||||
# To provide executable scripts, use entry points in preference to the
|
# To provide executable scripts, use entry points in preference to the
|
||||||
|
|
|
@ -36,14 +36,24 @@ git submodule update --init --recursive
|
||||||
Running the Tests
|
Running the Tests
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Before running the tests, the virtual machine must be running. It can be
|
Before running the tests, you must SSH into the VM. Start it with `vagrant up`
|
||||||
started by running `vagrant up`.
|
and SSH in with `vagrant ssh`. All following commands must be run inside the
|
||||||
|
VM.
|
||||||
|
|
||||||
The test suite can be run by running `./test`. Selected tests can be run by
|
First, you must install a version of Python to test against, using `pyenv
|
||||||
passing paths to the tests as arguments to `./test`.
|
install -s {version}`. You can choose any version you like, e.g. `3.8.1`. It
|
||||||
|
isn't particularly important to test against all supported versions of Python
|
||||||
|
in the VM, because they will be tested by CI. Once you've installed a specific
|
||||||
|
version of Python, activate it with `pyenv global {version}`.
|
||||||
|
|
||||||
Tests can be run with a specific Python version by running `./test --version
|
The VM mounts the Dotbot directory in `/dotbot` as read-only (you can make
|
||||||
<version>` - for example, `./test --version 3.4.3`.
|
edits on your host machine). You can run the test suite by `cd /dotbot/test`
|
||||||
|
and then running `./test`. Selected tests can be run by passing paths to the
|
||||||
|
tests as arguments, e.g. `./test tests/create.bash tests/defaults.bash`.
|
||||||
|
|
||||||
|
To debug tests, you can prepend the line `DEBUG=true` as the first line to any
|
||||||
|
individual test (a `.bash` file inside `test/tests`). This will enable printing
|
||||||
|
stdout/stderr.
|
||||||
|
|
||||||
When finished with testing, it is good to shut down the virtual machine by
|
When finished with testing, it is good to shut down the virtual machine by
|
||||||
running `vagrant halt`.
|
running `vagrant halt`.
|
||||||
|
|
4
test/Vagrantfile
vendored
4
test/Vagrantfile
vendored
|
@ -1,8 +1,8 @@
|
||||||
Vagrant.configure(2) do |config|
|
Vagrant.configure(2) do |config|
|
||||||
config.vm.box = 'debian/buster64'
|
config.vm.box = 'ubuntu/bionic64'
|
||||||
|
|
||||||
# sync by copying for isolation
|
# sync by copying for isolation
|
||||||
config.vm.synced_folder "..", "/dotbot", type: "rsync"
|
config.vm.synced_folder "..", "/dotbot", mount_options: ["ro"]
|
||||||
|
|
||||||
# disable default synced folder
|
# disable default synced folder
|
||||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
MAXRETRY=5
|
|
||||||
TIMEOUT=1
|
|
||||||
|
|
||||||
red() {
|
red() {
|
||||||
if [ -t 1 ]; then
|
if [ -t 1 ]; then
|
||||||
printf "\033[31m%s\033[0m\n" "$*"
|
printf "\033[31m%s\033[0m\n" "$*"
|
||||||
|
@ -26,52 +23,35 @@ yellow() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
check_prereqs() {
|
check_env() {
|
||||||
if ! (vagrant ssh -c 'exit') >/dev/null 2>&1; then
|
if [[ "$(whoami)" != "vagrant" && ( "${TRAVIS}" != true || "${CI}" != true ) ]]; then
|
||||||
>&2 echo "vagrant vm must be running."
|
die "tests must be run inside Travis or Vagrant"
|
||||||
return 1
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
until_success() {
|
|
||||||
local timeout=${TIMEOUT}
|
|
||||||
local attempt=0
|
|
||||||
while [ $attempt -lt $MAXRETRY ]; do
|
|
||||||
if ($@) >/dev/null 2>&1; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
sleep $timeout
|
|
||||||
timeout=$((timeout * 2))
|
|
||||||
attempt=$((attempt + 1))
|
|
||||||
done
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
wait_for_vagrant() {
|
|
||||||
until_success vagrant ssh -c 'exit'
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
vagrant ssh -c "
|
(
|
||||||
find . -not \\( \
|
if [ "$(whoami)" == "vagrant" ]; then
|
||||||
|
cd $HOME
|
||||||
|
find . -not \( \
|
||||||
-path './.pyenv' -o \
|
-path './.pyenv' -o \
|
||||||
-path './.pyenv/*' -o \
|
-path './.pyenv/*' -o \
|
||||||
-path './.bashrc' -o \
|
-path './.bashrc' -o \
|
||||||
-path './.profile' -o \
|
-path './.profile' -o \
|
||||||
-path './.ssh' -o \
|
-path './.ssh' -o \
|
||||||
-path './.ssh/*' \
|
-path './.ssh/*' \
|
||||||
\\) -delete" >/dev/null 2>&1
|
\) -delete >/dev/null 2>&1
|
||||||
|
else
|
||||||
|
find ~ -mindepth 1 -newermt "${date_stamp}" \
|
||||||
|
-not \( -path ~ -o -path "${BASEDIR}/*" \
|
||||||
|
-o -path ~/dotfiles \) \
|
||||||
|
-exec rm -rf {} +
|
||||||
|
fi
|
||||||
|
) || true
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
echo "initializing."
|
echo "initializing."
|
||||||
if ! vagrant ssh -c "pyenv local ${2}" >/dev/null 2>&1; then
|
|
||||||
if ! vagrant ssh -c "pyenv install -s ${2} && pyenv local ${2}" >/dev/null 2>&1; then
|
|
||||||
die "could not install python ${2}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
vagrant rsync >/dev/null 2>&1
|
|
||||||
tests_run=0
|
tests_run=0
|
||||||
tests_passed=0
|
tests_passed=0
|
||||||
tests_failed=0
|
tests_failed=0
|
||||||
|
@ -96,8 +76,7 @@ run_test() {
|
||||||
tests_run=$((tests_run + 1))
|
tests_run=$((tests_run + 1))
|
||||||
printf '[%d/%d] (%s)\n' "${tests_run}" "${tests_total}" "${1}"
|
printf '[%d/%d] (%s)\n' "${tests_run}" "${tests_total}" "${1}"
|
||||||
cleanup
|
cleanup
|
||||||
vagrant ssh -c "pyenv local ${2}" >/dev/null 2>&1
|
if (cd "${BASEDIR}/test/tests" && DOTBOT_TEST=true bash "${1}"); then
|
||||||
if vagrant ssh -c "cd /dotbot/test/tests && bash ${1}" 2>/dev/null; then
|
|
||||||
pass
|
pass
|
||||||
else
|
else
|
||||||
fail
|
fail
|
||||||
|
|
26
test/test
26
test/test
|
@ -1,37 +1,21 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
export BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
cd "${BASEDIR}"
|
cd "${BASEDIR}/test"
|
||||||
. "./driver-lib.bash"
|
. "./driver-lib.bash"
|
||||||
|
|
||||||
|
date_stamp="$(date --rfc-3339=ns)"
|
||||||
start="$(date +%s)"
|
start="$(date +%s)"
|
||||||
|
|
||||||
check_prereqs || die "prerequisites unsatsfied."
|
check_env
|
||||||
|
|
||||||
# command line options
|
|
||||||
while [[ $# > 1 ]]
|
|
||||||
do
|
|
||||||
key="${1}"
|
|
||||||
case $key in
|
|
||||||
-v|--version)
|
|
||||||
VERSION="${2}"
|
|
||||||
shift && shift
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
# unknown option
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
VERSION="${VERSION:-3.6.4}"
|
|
||||||
|
|
||||||
declare -a tests=()
|
declare -a tests=()
|
||||||
|
|
||||||
if [ $# -eq 0 ]; then
|
if [ $# -eq 0 ]; then
|
||||||
while read file; do
|
while read file; do
|
||||||
tests+=("${file}")
|
tests+=("${file}")
|
||||||
done < <(find tests -type f -name '*.bash')
|
done < <(find tests -type f -name '*.bash' | sort)
|
||||||
else
|
else
|
||||||
tests=("$@")
|
tests=("$@")
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
DEBUG=${DEBUG:-false}
|
DEBUG=${DEBUG:-false}
|
||||||
USE_VAGRANT=${USE_VAGRANT:-true}
|
DOTBOT_EXEC="${BASEDIR}/bin/dotbot"
|
||||||
DOTBOT_EXEC=${DOTBOT_EXEC:-"python /dotbot/bin/dotbot"}
|
|
||||||
DOTFILES="/home/$(whoami)/dotfiles"
|
DOTFILES="/home/$(whoami)/dotfiles"
|
||||||
INSTALL_CONF='install.conf.yaml'
|
INSTALL_CONF='install.conf.yaml'
|
||||||
INSTALL_CONF_JSON='install.conf.json'
|
INSTALL_CONF_JSON='install.conf.json'
|
||||||
|
@ -29,17 +28,15 @@ test_expect_failure() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
check_vm() {
|
check_env() {
|
||||||
if [ "$(whoami)" != "vagrant" ]; then
|
if [ "${DOTBOT_TEST}" != "true" ]; then
|
||||||
>&2 echo "test can't run outside vm!"
|
>&2 echo "test must be run by test driver"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
if ${USE_VAGRANT}; then
|
check_env
|
||||||
check_vm
|
|
||||||
fi
|
|
||||||
echo "${test_description}"
|
echo "${test_description}"
|
||||||
mkdir -p "${DOTFILES}"
|
mkdir -p "${DOTFILES}"
|
||||||
cd
|
cd
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# For debug only:
|
|
||||||
# export DEBUG=true
|
|
||||||
# set -x
|
|
||||||
# set -v
|
|
||||||
|
|
||||||
export BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
||||||
|
|
||||||
# Prevent execution outside of Travis CI builds
|
|
||||||
if [[ "${TRAVIS}" != true || "${CI}" != true ]]; then
|
|
||||||
echo "Error: `basename "$0"` should only be used on Travis"
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Travis runs do not rely on Vagrant
|
|
||||||
export USE_VAGRANT=false
|
|
||||||
export DOTBOT_EXEC="${BASEDIR}/bin/dotbot"
|
|
||||||
|
|
||||||
cd "${BASEDIR}"
|
|
||||||
. "test/driver-lib.bash"
|
|
||||||
|
|
||||||
travis_initialize() {
|
|
||||||
echo "initializing."
|
|
||||||
tests_run=0
|
|
||||||
tests_passed=0
|
|
||||||
tests_failed=0
|
|
||||||
tests_total="${1}"
|
|
||||||
local plural="" && [ "${tests_total}" -gt 1 ] && plural="s"
|
|
||||||
printf -- "running %d test%s...\n\n" "${tests_total}" "${plural}"
|
|
||||||
}
|
|
||||||
|
|
||||||
travis_cleanup() {
|
|
||||||
# Remove all dotfiles installed since the start, ignoring the main
|
|
||||||
# dotfiles directory, and the dotbot source directory
|
|
||||||
find ~ -mindepth 1 -newermt "${date_stamp}" \
|
|
||||||
-not \( -path ~ -o -path "${BASEDIR}/*" \
|
|
||||||
-o -path ~/dotfiles \) \
|
|
||||||
-exec rm -rf {} +
|
|
||||||
}
|
|
||||||
|
|
||||||
travis_run_test() {
|
|
||||||
tests_run=$((tests_run + 1))
|
|
||||||
printf '[%d/%d] (%s)\n' "${tests_run}" "${tests_total}" "${1}"
|
|
||||||
cd ${BASEDIR}/test/tests
|
|
||||||
if bash ${1} ; then
|
|
||||||
pass
|
|
||||||
else
|
|
||||||
fail
|
|
||||||
fi
|
|
||||||
travis_cleanup || die "unable to clean up system."
|
|
||||||
}
|
|
||||||
|
|
||||||
date_stamp="$(date --rfc-3339=ns)"
|
|
||||||
start="$(date +%s)"
|
|
||||||
|
|
||||||
declare -a tests=()
|
|
||||||
|
|
||||||
if [ $# -eq 0 ]; then
|
|
||||||
while read file; do
|
|
||||||
tests+=("${file}")
|
|
||||||
done < <(find ${BASEDIR}/test/tests -type f -name '*.bash')
|
|
||||||
else
|
|
||||||
tests=("$@")
|
|
||||||
fi
|
|
||||||
|
|
||||||
travis_initialize "${#tests[@]}"
|
|
||||||
|
|
||||||
for file in "${tests[@]}"; do
|
|
||||||
travis_run_test "$(basename "${file}")"
|
|
||||||
done
|
|
||||||
|
|
||||||
if report; then
|
|
||||||
ret=0
|
|
||||||
else
|
|
||||||
ret=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "(tests run in $(($(date +%s) - start)) seconds)"
|
|
||||||
exit ${ret}
|
|
19
test/tests/clean-default.bash
Normal file
19
test/tests/clean-default.bash
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
test_description='clean uses default unless overridden'
|
||||||
|
. '../test-lib.bash'
|
||||||
|
|
||||||
|
test_expect_success 'setup' '
|
||||||
|
ln -s /nowhere ~/.g
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'run' '
|
||||||
|
run_dotbot <<EOF
|
||||||
|
- clean:
|
||||||
|
~/nonexistent:
|
||||||
|
force: true
|
||||||
|
~/:
|
||||||
|
EOF
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'test' '
|
||||||
|
test -h ~/.g
|
||||||
|
'
|
34
test/tests/clean-recursive.bash
Normal file
34
test/tests/clean-recursive.bash
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
test_description='clean removes recursively'
|
||||||
|
. '../test-lib.bash'
|
||||||
|
|
||||||
|
test_expect_success 'setup' '
|
||||||
|
mkdir -p ~/a/b
|
||||||
|
ln -s /nowhere ~/c
|
||||||
|
ln -s /nowhere ~/a/d
|
||||||
|
ln -s /nowhere ~/a/b/e
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'run' '
|
||||||
|
run_dotbot <<EOF
|
||||||
|
- clean:
|
||||||
|
~/:
|
||||||
|
force: true
|
||||||
|
EOF
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'test' '
|
||||||
|
! test -h ~/c && test -h ~/a/d && test -h ~/a/b/e
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'run 2' '
|
||||||
|
run_dotbot <<EOF
|
||||||
|
- clean:
|
||||||
|
~/:
|
||||||
|
force: true
|
||||||
|
recursive: true
|
||||||
|
EOF
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'test 2' '
|
||||||
|
! test -h ~/a/d && ! test -h ~/a/b/e
|
||||||
|
'
|
|
@ -1,18 +1,16 @@
|
||||||
test_description='can find python executable with different names'
|
test_description='can find python executable with different names'
|
||||||
. '../test-lib.bash'
|
. '../test-lib.bash'
|
||||||
|
|
||||||
if ${USE_VAGRANT}; then
|
|
||||||
DOTBOT_EXEC="/dotbot/bin/dotbot" # revert to calling it as a shell script
|
|
||||||
fi
|
|
||||||
|
|
||||||
# the test machine needs to have a binary named `python`
|
# the test machine needs to have a binary named `python`
|
||||||
test_expect_success 'setup' '
|
test_expect_success 'setup' '
|
||||||
mkdir ~/tmp_bin &&
|
mkdir ~/tmp_bin &&
|
||||||
(
|
(
|
||||||
IFS=:
|
IFS=:
|
||||||
for p in $PATH; do
|
for p in $PATH; do
|
||||||
find $p -maxdepth 1 -mindepth 1 -exec sh -c \
|
if [ -d $p ]; then
|
||||||
|
find $p -maxdepth 1 -mindepth 1 -exec sh -c \
|
||||||
'"'"'ln -sf {} $HOME/tmp_bin/$(basename {})'"'"' \;
|
'"'"'ln -sf {} $HOME/tmp_bin/$(basename {})'"'"' \;
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
) &&
|
) &&
|
||||||
rm -f ~/tmp_bin/python &&
|
rm -f ~/tmp_bin/python &&
|
||||||
|
|
20
test/tests/link-canonicalize.bash
Normal file
20
test/tests/link-canonicalize.bash
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
test_description='linking canonicalizes path by default'
|
||||||
|
. '../test-lib.bash'
|
||||||
|
|
||||||
|
test_expect_success 'setup' '
|
||||||
|
echo "apple" > ${DOTFILES}/f &&
|
||||||
|
ln -s dotfiles dotfiles-symlink
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'run' '
|
||||||
|
cat > "${DOTFILES}/${INSTALL_CONF}" <<EOF
|
||||||
|
- link:
|
||||||
|
~/.f:
|
||||||
|
path: f
|
||||||
|
EOF
|
||||||
|
${DOTBOT_EXEC} -c dotfiles-symlink/${INSTALL_CONF}
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'test' '
|
||||||
|
[ "$(readlink ~/.f | cut -d/ -f4-)" = "dotfiles/f" ]
|
||||||
|
'
|
23
test/tests/link-ignore-missing.bash
Normal file
23
test/tests/link-ignore-missing.bash
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
test_description='link is created even if source is missing'
|
||||||
|
. '../test-lib.bash'
|
||||||
|
|
||||||
|
test_expect_failure 'run' '
|
||||||
|
run_dotbot <<EOF
|
||||||
|
- link:
|
||||||
|
~/missing_link:
|
||||||
|
path: missing
|
||||||
|
EOF
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'run 2' '
|
||||||
|
run_dotbot <<EOF
|
||||||
|
- link:
|
||||||
|
~/missing_link:
|
||||||
|
path: missing
|
||||||
|
ignore-missing: true
|
||||||
|
EOF
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'test' '
|
||||||
|
test -L ~/missing_link
|
||||||
|
'
|
23
test/tests/link-no-canonicalize.bash
Normal file
23
test/tests/link-no-canonicalize.bash
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
test_description='linking path canonicalization can be disabled'
|
||||||
|
. '../test-lib.bash'
|
||||||
|
|
||||||
|
test_expect_success 'setup' '
|
||||||
|
echo "apple" > ${DOTFILES}/f &&
|
||||||
|
ln -s dotfiles dotfiles-symlink
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'run' '
|
||||||
|
cat > "${DOTFILES}/${INSTALL_CONF}" <<EOF
|
||||||
|
- defaults:
|
||||||
|
link:
|
||||||
|
canonicalize-path: false
|
||||||
|
- link:
|
||||||
|
~/.f:
|
||||||
|
path: f
|
||||||
|
EOF
|
||||||
|
${DOTBOT_EXEC} -c ./dotfiles-symlink/${INSTALL_CONF}
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'test' '
|
||||||
|
[ "$(readlink ~/.f | cut -d/ -f4-)" = "dotfiles-symlink/f" ]
|
||||||
|
'
|
|
@ -4,11 +4,7 @@ test_description='install shim works'
|
||||||
test_expect_success 'setup' '
|
test_expect_success 'setup' '
|
||||||
cd ${DOTFILES}
|
cd ${DOTFILES}
|
||||||
git init
|
git init
|
||||||
if ${USE_VAGRANT}; then
|
git submodule add ${BASEDIR} dotbot
|
||||||
git submodule add /dotbot dotbot
|
|
||||||
else
|
|
||||||
git submodule add ${BASEDIR} dotbot
|
|
||||||
fi
|
|
||||||
cp ./dotbot/tools/git-submodule/install .
|
cp ./dotbot/tools/git-submodule/install .
|
||||||
echo "pear" > ${DOTFILES}/foo
|
echo "pear" > ${DOTFILES}/foo
|
||||||
'
|
'
|
||||||
|
@ -18,9 +14,6 @@ cat > ${DOTFILES}/install.conf.yaml <<EOF
|
||||||
- link:
|
- link:
|
||||||
~/.foo: foo
|
~/.foo: foo
|
||||||
EOF
|
EOF
|
||||||
if ! ${USE_VAGRANT}; then
|
|
||||||
sed -i "" "1 s/sh$/python/" ${DOTFILES}/dotbot/bin/dotbot
|
|
||||||
fi
|
|
||||||
${DOTFILES}/install
|
${DOTFILES}/install
|
||||||
'
|
'
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue