Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ TESTS = \
test/rcdn-hooks.t \
test/rcdn-hooks-run-in-situ.t \
test/rcdn-hooks-run-in-order.t \
test/rcdn-dangling-symlinks.t \
test/rcup-hooks.t \
test/rcup-hooks-run-in-situ.t \
test/rcup-hooks-run-in-order.t \
Expand Down
122 changes: 120 additions & 2 deletions bin/rcdn.in
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,116 @@ remove_link() {
fi
}

is_selected_file() {
local dest="$1"
local selected_file
local rc_file="$(de_dot "$dest")"

if [ -z "$FILES" ]; then
return 0
fi

for selected_file in $FILES; do
if [ "x$selected_file" = "x$rc_file" ]; then
return 0
fi
done

return 1
}

is_managed_target() {
local target="$1"
local dotfiles_dir

case "$target" in
/*) ;;
*) return 1 ;;
esac

for dotfiles_dir in $RESOLVED_DOTFILES_DIRS; do
if [ "x$target" = "x$dotfiles_dir" ]; then
return 0
fi

case "$target" in
"$dotfiles_dir"/*) return 0 ;;
esac
done

return 1
}

is_dotfiles_path() {
local file="$1"
local dotfiles_dir

for dotfiles_dir in $RESOLVED_DOTFILES_DIRS; do
if [ "x$file" = "x$dotfiles_dir" ]; then
return 0
fi

case "$file" in
"$dotfiles_dir"/*) return 0 ;;
esac
done

return 1
}

resolve_dotfiles_dirs() {
local relative_root_dir="$PWD"
local dotfiles_dir
local selected_dirs="${SELECTED_DOTFILES_DIRS:-$DOTFILES_DIRS}"

for dotfiles_dir in $selected_dirs; do
cd -- "$relative_root_dir"
dotfiles_dir="$(eval echo "$dotfiles_dir")"

if is_relative "$dotfiles_dir"; then
dotfiles_dir="$PWD/$dotfiles_dir"
fi

RESOLVED_DOTFILES_DIRS="$(append_variable "$RESOLVED_DOTFILES_DIRS" "$dotfiles_dir")"
done

cd -- "$relative_root_dir"
}

remove_dangling_links() {
local dangling_link
local target
local saved_ifs="$IFS"

resolve_dotfiles_dirs
$DEBUG "resolved dotfiles dirs: $RESOLVED_DOTFILES_DIRS"

IFS='
'
for dangling_link in $(find "$DEST_DIR" -type l ! -exec test -e {} \; -print); do
target="$(readlink "$dangling_link")"

if is_dotfiles_path "$dangling_link"; then
continue
fi

if ! is_managed_target "$target"; then
continue
fi

if ! is_selected_file "$dangling_link"; then
continue
fi

remove_link "$dangling_link" "$dangling_link"
done
IFS="$saved_ifs"
}

show_help() {
local exit_code=${1:-0}

$PRINT "Usage: rcdn [-hqVv] [-B HOSTNAME] [-d DOT_DIR] [-I EXCL_PAT] [-S EXCL_PAT] [-s EXCL_PAT] [-t TAG] [-U EXCL_PAT] [-u EXCL_PAT] [-x EXCL_PAT]"
$PRINT "Usage: rcdn [-DhqVv] [-B HOSTNAME] [-d DOT_DIR] [-I EXCL_PAT] [-S EXCL_PAT] [-s EXCL_PAT] [-t TAG] [-U EXCL_PAT] [-u EXCL_PAT] [-x EXCL_PAT]"
$PRINT "see rcdn(1) and rcm(7) for more details"

exit $exit_code
Expand All @@ -43,9 +149,11 @@ handle_command_line() {
local undotted=
local never_undotted=
local hostname=
local remove_dangling=0

while getopts :VqvhIKk:x:S:s:U:u:t:d:B: opt; do
while getopts :DVqvhIKk:x:S:s:U:u:t:d:B: opt; do
case "$opt" in
D) remove_dangling=1 ;;
h) show_help ;;
B) hostname="$OPTARG" ;;
I) includes="$(append_variable "$includes" "$OPTARG")" ;;
Expand Down Expand Up @@ -73,6 +181,9 @@ handle_command_line() {
dotfiles_dirs="${dotfiles_dirs:-$DOTFILES_DIRS}"
files="$@"
RUN_HOOKS="$run_hooks"
FILES="$files"
REMOVE_DANGLING=$remove_dangling
SELECTED_DOTFILES_DIRS="$dotfiles_dirs"

for tag in "$tags"; do
LS_ARGS="$LS_ARGS -t \"$tag\""
Expand Down Expand Up @@ -105,9 +216,13 @@ handle_command_line() {
}

LS_ARGS=-F
RESOLVED_DOTFILES_DIRS=

handle_command_line "$@"
: ${DOTFILES_DIRS:=$DOTFILES_DIRS $DEFAULT_DOTFILES_DIR}
if [ -z "$SELECTED_DOTFILES_DIRS" ]; then
SELECTED_DOTFILES_DIRS="$DOTFILES_DIRS"
fi

run_hooks pre down

Expand All @@ -127,4 +242,7 @@ for dest_and_src in $dests_and_srcs; do
done

IFS="$saved_ifs"
if [ $REMOVE_DANGLING -eq 1 ]; then
remove_dangling_links
fi
run_hooks post down
10 changes: 9 additions & 1 deletion man/rcdn.1
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
.Nd remove dotfiles as managed by rcm
.Sh SYNOPSIS
.Nm rcdn
.Op Fl hKkqVv
.Op Fl DhKkqVv
.Op Fl B Ar hostname
.Op Fl d Ar dir
.Op Fl I Ar excl_pat
Expand Down Expand Up @@ -65,6 +65,14 @@ as the host-specific directory instead of computing it
remove rc files from the
.Ar DIR .
This can be specified multiple times.
.It Fl D
remove dangling symlinks that are managed by
.Xr rcm 7 .
Managed dangling symlinks are symlinks in
the destination directory
whose targets are inside the selected dotfiles directories. When
.Ar files
are provided, only dangling symlinks matching those files are removed.
.It Fl h
show usage instructions.
.It Fl I Ar EXCL_PAT
Expand Down
37 changes: 37 additions & 0 deletions test/rcdn-dangling-symlinks.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
$ . "$TESTDIR/helper.sh"

Without -D, rcdn should not remove managed dangling symlinks

$ touch .dotfiles/alpha
> rcup >/dev/null
> rm .dotfiles/alpha
> rcdn >/dev/null
$ assert "alpha should still be a dangling symlink" -h "$HOME/.alpha"

With -D, rcdn should remove managed dangling symlinks

$ rcdn -D >/dev/null
$ refute "alpha should be removed when -D is passed" -e "$HOME/.alpha"

With -D, rcdn should keep unmanaged dangling symlinks

$ ln -s "$HOME/not-managed-target" "$HOME/.outside"
$ rcdn -D >/dev/null
$ assert "outside should still be a dangling symlink" -h "$HOME/.outside"

With -D and FILE arguments, cleanup should be restricted to those files

$ touch .dotfiles/one .dotfiles/two
> rcup >/dev/null
> rm .dotfiles/one .dotfiles/two
$ assert "one should be dangling symlink" -h "$HOME/.one"
$ assert "two should be dangling symlink" -h "$HOME/.two"
$ rcdn -D one >/dev/null
$ refute "one should be removed" -e "$HOME/.one"
$ assert "two should remain because it was not requested" -h "$HOME/.two"

With -D, rcdn should not remove dangling symlinks inside source dotfiles dirs

$ ln -s "$HOME/.dotfiles/missing-in-source" "$HOME/.dotfiles/internal-dangling"
$ rcdn -D >/dev/null
$ assert "internal source-dir dangling link should not be removed" -h "$HOME/.dotfiles/internal-dangling"