diff --git a/README.md b/README.md index 20c69d2..1aa5fa8 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,7 @@ Available extended configuration parameters: | `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) | | `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) | | `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) | diff --git a/dotbot/cli.py b/dotbot/cli.py index 2680acf..77bd439 100644 --- a/dotbot/cli.py +++ b/dotbot/cli.py @@ -73,10 +73,10 @@ def main(): if not isinstance(tasks, list): raise ReadingError('Configuration file must be a list of tasks') if options.base_directory: - base_directory = options.base_directory + base_directory = os.path.abspath(options.base_directory) else: # 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) dispatcher = Dispatcher(base_directory) success = dispatcher.dispatch(tasks) diff --git a/dotbot/context.py b/dotbot/context.py index b2dbd6c..8c42d47 100644 --- a/dotbot/context.py +++ b/dotbot/context.py @@ -1,4 +1,5 @@ import copy +import os class Context(object): ''' @@ -13,8 +14,11 @@ class Context(object): def set_base_directory(self, base_directory): self._base_directory = base_directory - def base_directory(self): - return self._base_directory + def base_directory(self, canonical_path=True): + base_directory = self._base_directory + if canonical_path: + base_directory = os.path.realpath(base_directory) + return base_directory def set_defaults(self, defaults): self._defaults = defaults diff --git a/dotbot/dispatcher.py b/dotbot/dispatcher.py index d1a4f95..36eac02 100644 --- a/dotbot/dispatcher.py +++ b/dotbot/dispatcher.py @@ -10,8 +10,8 @@ class Dispatcher(object): self._load_plugins() def _setup_context(self, base_directory): - path = os.path.abspath(os.path.realpath( - os.path.expanduser(base_directory))) + path = os.path.abspath( + os.path.expanduser(base_directory)) if not os.path.exists(path): raise DispatchError('Nonexistent base directory') self._context = Context(path) diff --git a/dotbot/plugins/link.py b/dotbot/plugins/link.py index d38c0ab..82a61ce 100644 --- a/dotbot/plugins/link.py +++ b/dotbot/plugins/link.py @@ -26,6 +26,7 @@ class Link(dotbot.Plugin): for destination, source in links.items(): destination = os.path.expandvars(destination) relative = defaults.get('relative', False) + canonical_path = defaults.get('canonicalize-path', True) force = defaults.get('force', False) relink = defaults.get('relink', False) create = defaults.get('create', False) @@ -36,6 +37,7 @@ class Link(dotbot.Plugin): # extended config test = source.get('if', test) relative = source.get('relative', relative) + canonical_path = source.get('canonicalize-path', canonical_path) force = source.get('force', force) relink = source.get('relink', relink) create = source.get('create', create) @@ -68,8 +70,8 @@ class Link(dotbot.Plugin): if create: success &= self._create(destination) if force or relink: - success &= self._delete(path, destination, relative, force) - success &= self._link(path, destination, relative, ignore_missing) + success &= self._delete(path, destination, relative, canonical_path, force) + success &= self._link(path, destination, relative, canonical_path, ignore_missing) else: self._log.lowinfo("Globs from '" + path + "': " + str(glob_results)) glob_base = path[:glob_star_loc] @@ -79,8 +81,8 @@ class Link(dotbot.Plugin): if create: success &= self._create(glob_link_destination) if force or relink: - success &= self._delete(glob_full_item, glob_link_destination, relative, force) - success &= self._link(glob_full_item, glob_link_destination, relative, ignore_missing) + success &= self._delete(glob_full_item, glob_link_destination, relative, canonical_path, force) + success &= self._link(glob_full_item, glob_link_destination, relative, canonical_path, ignore_missing) else: if create: success &= self._create(destination) @@ -94,8 +96,8 @@ class Link(dotbot.Plugin): (destination, path)) continue if force or relink: - success &= self._delete(path, destination, relative, force) - success &= self._link(path, destination, relative, ignore_missing) + success &= self._delete(path, destination, relative, canonical_path, force) + success &= self._link(path, destination, relative, canonical_path, ignore_missing) if success: self._log.info('All links have been set up') else: @@ -159,9 +161,9 @@ class Link(dotbot.Plugin): self._log.lowinfo('Creating directory %s' % parent) return success - def _delete(self, source, path, relative, force): + def _delete(self, source, path, relative, canonical_path, force): 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) if relative: source = self._relative_path(source, fullpath) @@ -195,7 +197,7 @@ class Link(dotbot.Plugin): destination_dir = os.path.dirname(destination) return os.path.relpath(source, destination_dir) - def _link(self, source, link_name, relative, ignore_missing): + def _link(self, source, link_name, relative, canonical_path, ignore_missing): ''' Links link_name to source. @@ -203,7 +205,8 @@ class Link(dotbot.Plugin): ''' success = False 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: source = self._relative_path(absolute_source, destination) else: diff --git a/test/tests/link-canonicalize.bash b/test/tests/link-canonicalize.bash new file mode 100644 index 0000000..34015c8 --- /dev/null +++ b/test/tests/link-canonicalize.bash @@ -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}" < ${DOTFILES}/f && +ln -s dotfiles dotfiles-symlink +' + +test_expect_success 'run' ' +cat > "${DOTFILES}/${INSTALL_CONF}" <