initial commit.
This commit is contained in:
commit
72cbc59f70
|
@ -0,0 +1,20 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright 2020 "yafox"
|
||||||
|
|
||||||
|
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,26 @@
|
||||||
|
lmr
|
||||||
|
====
|
||||||
|
|
||||||
|
pronounced "lemur." an abbreviation of "link merge."
|
||||||
|
|
||||||
|
merges the contents of one directory into another using filesystem links.
|
||||||
|
|
||||||
|
can reverse a merge as long as the contents of the source directory have
|
||||||
|
not changed since the merge was completed and no files were overwritten
|
||||||
|
during the merge. if files were removed from the source directory, "dangling"
|
||||||
|
symlinks will be left behind.
|
||||||
|
|
||||||
|
lmr exists for much the same reason as gnu stow, but has significantly fewer
|
||||||
|
features and operates more like a recursive `ln` than a simplified `stow`.
|
||||||
|
|
||||||
|
lmr is implemented as a single, 80 SLOC long, POSIX-compliant shell script.
|
||||||
|
|
||||||
|
usage: lmr [-c] [-f] [-s] [-t|-u] [--] <src> <dest>
|
||||||
|
|
||||||
|
options:
|
||||||
|
-c: check for file conflicts. link nothing.
|
||||||
|
-f: force link creation, even if it overwrites files.
|
||||||
|
-s: use symlinks instead of hard links.
|
||||||
|
-t: exit if it seems a lmr has already been done between <src> and <dest>.
|
||||||
|
-u: try to undo a previous lmr from <src> to <dest>.
|
||||||
|
--: optional "end of flags" marker.
|
|
@ -0,0 +1,115 @@
|
||||||
|
#!/bin/sh -e
|
||||||
|
|
||||||
|
[ -n "$1" ] || { \
|
||||||
|
echo "
|
||||||
|
usage: $(basename $0) [-c] [-f] [-s] [-t|-u] [--] <src> <dest>
|
||||||
|
|
||||||
|
options:
|
||||||
|
-c: check for file conflicts. link nothing.
|
||||||
|
-f: force link creation, even if it overwrites files.
|
||||||
|
-s: use symlinks instead of hard links.
|
||||||
|
-t: exit if it seems a lmr has already been done between <src> and <dest>.
|
||||||
|
-u: try to undo a previous lmr from <src> to <dest>.
|
||||||
|
--: optional "end of flags" marker.
|
||||||
|
" && exit 0; }
|
||||||
|
|
||||||
|
# if this is running on an interactive terminal (as opposed to in a script), if
|
||||||
|
# tput is installed, and if tput knows some color codes, then set color values.
|
||||||
|
if [ -t 1 ] && [ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]; then
|
||||||
|
_clr="$(tput sgr0)"
|
||||||
|
_blu="$(tput setaf 6)"
|
||||||
|
_ylw="$(tput setaf 3)"
|
||||||
|
_red="$(tput setaf 1)"
|
||||||
|
fi
|
||||||
|
log() { echo "$_blu[LOG]$_clr $@"; }
|
||||||
|
wrn() { echo "$_ylw[WRN]$_clr $@" >&2; }
|
||||||
|
err() { echo "$_red[ERR]$_clr $@" >&2; exit 1; }
|
||||||
|
|
||||||
|
# -e checks if a file exists, but also dereferences symlinks and returns false
|
||||||
|
# if the link's target does not exist. -L returns true if the file exists and
|
||||||
|
# is a symlink; it does not dereference symlinks.
|
||||||
|
exists() { [ -e "$1" ] || [ -L "$1" ]; }
|
||||||
|
|
||||||
|
linked() {
|
||||||
|
# -ef returns true if the files exist and refer to the same file.
|
||||||
|
{ [ -z "$soft" ] && [ "$1" -ef "$2" ]; } \
|
||||||
|
|| [ "$1" = "$(readlink "$2" 2> /dev/null)" ] \
|
||||||
|
|| return 1
|
||||||
|
}
|
||||||
|
export linked
|
||||||
|
|
||||||
|
while [ "$1" != "${1#-}" ]; do
|
||||||
|
case "$1" in
|
||||||
|
-c) check="yes";;
|
||||||
|
-f) flags="$flags -f";;
|
||||||
|
-s) soft="yes";;
|
||||||
|
-t) test="yes";;
|
||||||
|
-u) undo="yes";;
|
||||||
|
--) break;;
|
||||||
|
*) err "unrecognized option '$1'";;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
src="$1"
|
||||||
|
dest="$2"
|
||||||
|
|
||||||
|
[ -d "$1" ] || err "not a directory: $1"
|
||||||
|
[ -d "$2" ] || err "not a directory: $2"
|
||||||
|
|
||||||
|
if [ -z "$undo" ]; then
|
||||||
|
[ -z "$soft" ] && flags="$flags -P" || flags="$flags -s"
|
||||||
|
|
||||||
|
find "$src" -type d ! -path "$src" -exec sh -c \
|
||||||
|
"mkdir -p \$(echo \"$dest/\${0#$src}\" | tr -s '/')" {} \;
|
||||||
|
|
||||||
|
find "$src" \( -type f -o -type l \) | while read lnsrc; do
|
||||||
|
lndest="$dest/${lnsrc#$src}"
|
||||||
|
|
||||||
|
if [ -z "$test" ]; then
|
||||||
|
linked "$lnsrc" "$lndest" \
|
||||||
|
&& exit 0 \
|
||||||
|
|| exit 1
|
||||||
|
|
||||||
|
elif [ -z "$check" ]; then
|
||||||
|
{ ! exists "$lndest"; } \
|
||||||
|
|| linked "$lnsrc" "$lndest" \
|
||||||
|
|| wrn "conflict: ${lnsrc#$src}"
|
||||||
|
|
||||||
|
else
|
||||||
|
ln$flags "$lnsrc" "$lndest"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
else
|
||||||
|
# delete all files in the destination directory which are either soft or
|
||||||
|
# hard links (depending on whether the -s flag was set) to files in the
|
||||||
|
# source directory.
|
||||||
|
find "$src" \( -type f -o -type l \) | while read lnsrc; do
|
||||||
|
lndest="$dest/${lnsrc#$src}"
|
||||||
|
|
||||||
|
exists "$lndest" || continue;
|
||||||
|
|
||||||
|
# only remove the file in the "destination" directory if it is a hard or
|
||||||
|
# a soft link to the source file.
|
||||||
|
{ ! linked "$lnsrc" "$lndest"; } || rm "$lndest"
|
||||||
|
done
|
||||||
|
|
||||||
|
# recursively remove any empty directories in the destination directory
|
||||||
|
# which also exist in the source directory. while no guarantee that these
|
||||||
|
# were created by a previous lmr operation, this seems a decent heuristic.
|
||||||
|
# reverse-sorting by path length to ensure "leaves" are processed first.
|
||||||
|
find "$src" -type d ! -path "$src" \
|
||||||
|
| awk '{ print length() "\t" $0 | "sort -nr" }' \
|
||||||
|
| cut -f2 \
|
||||||
|
| while read dir; do
|
||||||
|
destdir="$dest/${dir#$src}"
|
||||||
|
|
||||||
|
# avoid "directory not empty" errors below.
|
||||||
|
[ -d "$destdir" ] && [ -z "$(ls -A $destdir/)" ] || continue;
|
||||||
|
|
||||||
|
# rmdir will only delete empty directories.
|
||||||
|
# use it in case the line above fails to detect something.
|
||||||
|
rmdir "$destdir" || true # continue even if this fails for some reason.
|
||||||
|
done
|
||||||
|
fi
|
|
@ -0,0 +1,9 @@
|
||||||
|
SRCDIR = $(dir $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||||
|
PREFIX ?= /usr
|
||||||
|
DESTDIR ?= $(PREFIX)/bin
|
||||||
|
|
||||||
|
install:
|
||||||
|
cp -a $(SRCDIR)/lmr.sh $(DESTDIR)/lmr
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
rm $(DESTDIR)/lmr
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# SLOC is here defined as lines which are not comments or empty.
|
||||||
|
|
||||||
|
# find all *.sh files not under `pkg` that are not symbolic links, strip all
|
||||||
|
# trailing whitespace, then all leading whitespace, then all lines starting
|
||||||
|
# with '#', then all empty lines. then count the remaining lines.
|
||||||
|
|
||||||
|
find . -name "*.sh" ! -path "**/pkg/**" ! -type l \
|
||||||
|
| xargs sed 's/[[:space:]]*$//g; s/^[[:space:]]*//g; s/^#.*$//g; /^$/d' \
|
||||||
|
| wc -l - \
|
||||||
|
| cut -d' ' -f1
|
||||||
|
|
||||||
|
# note that this script's SLOC is also included in the count.
|
Loading…
Reference in New Issue