Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates for a faster link command. #119

Merged
merged 3 commits into from
Oct 14, 2014
Merged
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
84 changes: 52 additions & 32 deletions lib/commands/link.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,13 @@ function symlink {
ignore 'ignored' "$castle"
return $EX_SUCCESS
fi
oldIFS=$IFS
IFS=$'\n'
for filename in $(get_repo_files $repo/home); do
for filename in $(get_repo_files "$repo/home"); do
remote="$repo/home/$filename"
IFS=$oldIFS
local=$HOME/$filename
local="$HOME/$filename"

if [[ -e $local || -L $local ]]; then
# $local exists (but may be a dead symlink)
if [[ -L $local && $(readlink "$local") == $remote ]]; then
if [[ -L $local && $(readlink "$local") == "$remote" ]]; then
# $local symlinks to $remote.
if [[ -d $remote && ! -L $remote ]]; then
# If $remote is a directory -> legacy handling.
Expand Down Expand Up @@ -64,46 +61,69 @@ function symlink {
return $EX_SUCCESS
}

function get_repo_dirs {
# Loop through the files tracked by git and compute
# a list of their parent directories.
# The root of repo we are looking at, will not change.
local root=$1
# Check if this is the root invocation.
if [[ -n $2 ]]; then
# The relative path to the submodule:
relpath="$2/"
else
# First invocation, the repo_dir is just the root.
local repo_dir=$root
local relpath=''
fi
(
local path
while read path; do
printf "%s\n" "$relpath$path"
# Get all directory paths up to the root.
# We won't ever hit '/' here since ls-files
# always shows paths relative to the repo root.
while [[ $path =~ '/' ]]; do
path=$(dirname "$path")
printf "%s\n" "$relpath$path"
done
done < <(cd "$repo_dir" && git ls-files | xargs -I{} dirname "{}" | sort | uniq)
) | sort | uniq
}

function get_repo_files {
# This function descends recursively through all submodules
# of a repository and makes the paths returned by `git ls-files`
# relative to the root repo.
# All directory paths are computed as well.

# The root of repo we are looking at, will not change.
local root=$1
# Check if this is the root invocation
# Check if this is the root invocation.
if [[ -n $2 ]]; then
# The path to the current repo we are looking at
# The path to the current repo we are looking at:
repo_dir="$root/$2"
# The relative path to the submodule
# The relative path to the submodule:
relpath="$2/"
else
# First invocation, the repo dir is just the root
# First invocation, the repo_dir is just the root.
local repo_dir=$root
local relpath=''
fi
local paths=""
# Loop through the files tracked by git and compute
# a list of their parent directories.
for path in $(cd $repo_dir && git ls-files); do
# Don't add a newline to the beginning of the list
[[ -n $paths ]] && paths="$paths\n"
paths="$paths$relpath$path"
# Get all directory paths up to the root.
# We won't ever hit '/' here since ls-files
# always shows paths relative to the repo root.
while [[ $path =~ '/' ]]; do
path=$(dirname $path)
paths="$relpath$path\n$paths"
done
done
pending "list files" "$repo_dir" >&2
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be good to consider logging everything in homeshick to stderr instead of most things to stdout, and only errors to stderr (from what I remember of my brief look at log.sh, I think that is the current behavior). Logging to stderr in bash scripts enables you to log things without polluting stdout, which often gets piped around or evaluated by other functions. I would be happy to do the work if you feel like it is a good idea as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, and this line along with the success line at the end of the function lets the user know an operation is happening so impatient fools (like myself) are less likely to CTRL-C after a few seconds because it looks like nothing is happening 😄.

BTW, I really like the way you have done your logging with the pending / success functions - it looks really slick.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think anything other than progress should be logged to stdout, it should be machine-readable however (I created #70 a long time ago to track progress on that, still haven't gotten around to it :-)).

BTW, I really like the way you have done your logging with the pending / success functions - it looks really slick.

Thanks! It was fun to code as well, hehe.

(
# Get files (+ submodule path prefix).
local path
while read path; do
printf "%s\n" "$relpath$path"
done < <(cd "$repo_dir" && git ls-files)

# Loop through all submodule children (not descendants, i.e. immediate children)
# and invoke the function again, passing the relative path to the submodule as the 2nd arg
for submodule in $(cd $repo_dir; git submodule --quiet foreach 'printf "%s\n" "$path"'); do
paths="$(get_repo_files $root $relpath$submodule)\n$paths"
done
# Get all directories (+ submodule path prefix).
get_repo_dirs "$root" "$relpath"

printf "$paths" | sort | uniq
# Recurse on all submodule children (not descendants, i.e. immediate
# children), passing the relative path to the submodule as the 2nd arg.
for submodule in $(cd "$repo_dir"; git submodule --quiet foreach 'printf "%s\n" "$path"'); do
get_repo_files "$root" "$relpath$submodule"
done
) | sort | uniq
success "list files" "$repo_dir" >&2
}
15 changes: 7 additions & 8 deletions test/suites/link.bats
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,16 @@ EOF
castle 'dotfiles'
mkdir -p $HOME/.config/bar.dir
cat > $HOME/.config/foo.conf <<EOF
#I am just a regular foo.conf file
#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
#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" ]
Expand Down Expand Up @@ -161,14 +161,13 @@ EOF

@test 'fail when linking file with newline' {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I stated earlier, I wasn't able to actually get files with newlines working, but the behavior changed enough to fail the existing test. On the plus side, if you DO try to link a file with a newline, you end up with a single link rather than 2, but in the process the file name is mangled.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's actually way better behavior 👍

castle 'rc-files'
touch "$HOMESICK/repos/rc-files/home/filename
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" ]
[ -L "$HOME/newline\"" ]
is_symlink $HOMESICK/repos/rc-files/home/\"filename $HOME/\"filename
is_symlink $HOMESICK/repos/rc-files/home/newline\" $HOME/newline\"
[ -L "$HOME/\"filenamennewline\"" ]
is_symlink "$HOMESICK/repos/rc-files/home/\"filenamennewline\"" "$HOME/\"filenamennewline\""
}

@test 'files ignored by git should not be linked' {
Expand Down