#!/usr/bin/env bash
# annex -- convenience subcommands for git-annex
# vim: ts=4:sw=4:et

# 2022-02-16 grawity: Now nocache makes any git-annex operation really slow.
# For example, `nocache git-annex find ...` on Midnight is 18s instead of just 2s.
# 2025-06-14 grawity: Seems to be fine now, at least on Ember.
#export ANNEX_SKIP_NOCACHE=1

# Search for annex-* subcommands in additional locations
export PATH=$PATH:${0%/*}
export PATH=$PATH:$HOME/bin/misc

. lib.bash || exit

annexes=(~/Attic/{Anime,Annex,Software,Videos})

if [[ "$2" == "--help" ]]; then
    exec git-annex help "$1" || exit
fi

cmd=$1; shift

if [[ $ANNEX_SKIP_NOCACHE ]]; then
    debug "nocache: disabled by environment"
elif [[ $cmd == @(add|copy|fsck|get|import|move|push|reinject) ]]; then
    lib=/usr/lib/nocache.so
    if [[ ! -f $lib ]]; then
        lib=/usr/lib/nocache/nocache.so
    fi
    if [[ -f $lib ]]; then
        if [[ ":$LD_PRELOAD:" != *:"$lib":* ]]; then
            debug "nocache: appending '$lib' to \$LD_PRELOAD"
            export LD_PRELOAD="$LD_PRELOAD${LD_PRELOAD:+:}$lib"
            export ANNEX_SKIP_NOCACHE=1
        else
            debug "nocache: found '$lib' in \$LD_PRELOAD"
        fi
    else
        warn "nocache: '$lib' not found; may hurt performance"
    fi
else
    debug "nocache: disabled for command '$cmd'"
fi

# Set some default configuration
if ! git config --get core.quotePath &> /dev/null; then
    git config core.quotePath false
fi

# Rewrite args to implement a few shortcuts, such as --!foo=bar
newargs=()
changes=0
for arg; do
    if [[ $arg == --lost ]]; then
        newargs+=(--not --copies=1)
        changes=1
    elif [[ $arg == --!lost ]]; then
        newargs+=(--copies=1)
        changes=1
    elif [[ $arg == --here ]]; then
        newargs+=(--in=here)
        changes=1
    elif [[ $arg == --!here ]]; then
        newargs+=(--not --in=here)
        changes=1
    elif [[ $arg == --*!=* ]]; then
        newargs+=(--not "${arg%%!=*}=${arg#*!=}")
        changes=1
    elif [[ $arg == --!* ]]; then
        newargs+=(--not "--${arg#--!}")
        changes=1
    else
        newargs+=("$arg")
    fi
done
if (( changes )); then
    debug "rewrote args:"
    debug "  from ${*@Q}"
    debug "  to ${newargs[*]@Q}"
    set -- "${newargs[@]}"
fi

# Rewrite args to expand remote names to their descriptions, so that
# "--in" could be used with repositories that aren't in .git/config.
if [[ $cmd != each ]]; then
    newargs=()
    changes=0
    for arg; do
        if [[ "$arg" == --in=* ]]; then
            arghead='--in='
            arg=${arg#'--in='}
        elif [[ "$newargs" && "${newargs[-1]}" == --in ]]; then
            arghead=''
        else
            newargs+=("$arg")
            continue
        fi
        if [[ $arg == here ]]; then
            :
        elif git config "remote.$arg.annex-uuid" >/dev/null; then
            :
        elif remote=$(annex-dwim-remote "$arg"); then
            if [[ "$arg" != "$remote" ]]; then
                debug "remote '$arg' guessed to be '$remote'"
                arg=$remote
                changes=1
            fi
        else
            warn "remote '$arg' could not be guessed"
        fi
        newargs+=("$arghead$arg")
    done
    if (( changes )); then
        debug "rewrote args:"
        debug "  from ${*@Q}"
        debug "  to ${newargs[*]@Q}"
        set -- "${newargs[@]}"
    fi
fi

case $cmd in
    each | help | init)
        ;;
    *)
        if ! git rev-parse --verify 'refs/heads/git-annex' >&/dev/null; then
            die "not in a git-annex repository"
        fi ;;
esac

case $cmd in
    @*)
        name=${cmd#@}
        # assume annex subcommand by default
        if [[ $1 == !* || $1 == git ]]; then
            set -- "${1#!}" "${@:2}"
        elif [[ $1 ]]; then
            set -- annex "$@"
        else
            set -- bash
        fi
        # determine remote path
        if ! path=$(git remote get-url "$name"); then
            err "remote '$name' not found"
        elif [[ $path == [!/]*:* ]]; then
            host=${path%%:*}
            path=${path#*:}
        elif [[ $path != /* ]]; then
            err "remote '$name' has a non-local URL '$path'"
        elif [[ ! -d $path ]]; then
            err "remote path '$path' does not exist"
        fi
        # determine local relative subdirectory
        # * `git rev-parse --show-prefix` does not work in bare (direct)
        #   repos even with `-c core.bare=false`, so use indirect methods
        gitdir=$(git rev-parse --git-dir)
        subdir=$(realpath --relative-to="$gitdir/.." "$PWD")
        # run command
        if [[ $host ]]; then
            path=${path%/}
            path=${path%.git}
            altpath=${path}.git
            path=$path/$subdir
            altpath=$altpath/$subdir
            lib:log "$cmd: running '$*' at $host:$path" >&2
            ssh -t "$host" ". ~/.profile &&
                        if [ -e ${altpath@Q} ]; then
                            cd ${altpath@Q}
                        else
                            cd ${path@Q}
                        fi && ${*@Q}"
        else
            path=$(realpath "$path/$subdir") \
                || die "subpath '$subdir' does not exist in remote '$name'"
            lib:log "$cmd: running '$*' in $path" >&2
            (cd "$path" && "$@")
        fi
        ;;
    each)
        if (( ! $# )); then
            set -- pwd
            quiet=1
        elif [[ $1 == !* || $1 == git ]]; then
            set -- "${1#!}" "${@:2}"
        else
            set -- annex "$@"
            lib:is_nested -= 1
        fi
        for dir in "${annexes[@]}"; do
            (( quiet )) || log2 "$cmd: running '$*' in $dir"
            (cd "$dir" && "${@//"{}"/"${dir##*/}"}")
            if (( r=$? )); then
                err "command '$*' returned $r (while in $dir)"
            fi
        done
        if (( errors )); then
            warn "command '$*' failed in at least one repository"
        fi
        ;;
    upgrade-index)
        dir=$(git rev-parse --git-dir)
        files=("$dir/index" "$dir/annex/index")
        echo "Before:"; du -hsc "${files[@]}"
        for f in "${files[@]}"; do
            GIT_INDEX_FILE=$f git update-index --index-version 4
        done
        echo "After:"; du -hsc "${files[@]}"
        ;;
    minimize)
        git diff --quiet || die "working tree is not clean"
        h=$(git rev-parse --verify HEAD) || die "could not resolve HEAD"
        m=$(git rev-parse --verify master) || die "could not resolve 'master'"
        o=$(git rev-parse --verify origin/master) || die "could not resolve 'origin/master'"
        [[ "$h" == "$m" ]] || die "something else than master is checked out"
        [[ "$m" == "$o" ]] || die "local master branch is not up-to-date with origin"
        confirm "minimize?" || exit
        do: git symbolic-ref HEAD refs/heads/master
        do: git update-ref -d refs/heads/master
        do: git read-tree --empty
        do: git clean -fdx
        ;;
    want-get)
        git annex find --want-get --not --in .
        ;;
    want-drop)
        git annex find --want-drop --in .
        ;;
    want-auto)
        git annex find --want-get --not --in . | sed $'s/.*/\e[;32mwant\e[m &/'
        git annex find --want-drop --in . | sed $'s/.*/\e[;31mdrop\e[m &/'
        ;;
    want-push)
        git annex find --in . --not --in "$@"
        ;;
    only-in)
        (( $# == 1 )) || warn "too many arguments (expected 1)"
        git annex find --in "$1" --not --copies 2
        ;;
    unsafe)
        annex where --not --copies semitrusted+:1 "$@"
        ;;
    addurls|registerurls)
        git annex add "$@"

        if (( $# )); then
            find "$@" -xtype f
        else
            git annex find --in here --not --in web
        fi | while read -r file; do
            if [[ -d $file ]]; then
                :
            elif [[ -L $file && ! -e $file ]]; then
                :
            elif [[ ! -f $file ]]; then
                warn "item '$file' is not a file"
            else
                url=$(attr -L -q -g xdg.origin.url "$file" 2>/dev/null)
                if [[ $url == https://dynasty-scans.com/* ]]; then
                    warn "ignoring blacklisted origin '$url' for file '$file'" >&2
                elif [[ $url == @(http|https|ftp)://* ]]; then
                    info "adding origin '$url' to file '$file'" >&2
                    echo "$url $file"
                elif [[ $url ]]; then
                    warn "ignoring non-web origin '$url' for file '$file'" >&2
                else
                    warn "no origin URL for file '$file'" >&2
                fi
            fi | git annex addurl --batch --with-files
        done
        ;;
    geturls)
        (( $# )) || set -- "--include=*"
        git annex find "$@" \
            | git annex whereis --batch --fast --json \
            | jq -r '. as {file: $file} | (.whereis, .untrusted) | .[].urls[] | "\($file) \(.)"'
        ;;
    geturls-json)
        (( $# )) || set -- "--include=*"
        git annex find "$@" \
            | git annex whereis --batch --fast --json \
            | jq '{file: .file,
                    key: .key,
                    urls: [(.whereis, .untrusted) | .[].urls[]]}'
        ;;
    geturls-sh)
        (( $# )) || set -- "--include=*"
        git annex find "$@" \
            | git annex whereis --batch --fast --json \
            | jq -r '@sh "git annex addurl --relaxed --file=\(.file) \((.whereis, .untrusted) | .[].urls[])"'
        ;;
    pruneurls)
        # pruneurls <regex> [<findargs>]
        regex=$1; shift
        [[ $regex && $regex != -* ]] || die "prune regex not specified"
        git annex find --in web "$@" \
            | git annex whereis --batch --fast --json \
            | jq -r --arg cond "$regex" '
                . as {file: $file, key: $key}
                | (.whereis, .untrusted)
                | .[].urls[]
                | select(test($cond))
                | "\($file) \(.)"
            ' \
            | git annex rmurl --batch
        ;;

    ci | commit)
        git config receive.denyCurrentBranch updateInstead
        git annex sync --no-pull --no-push --no-content
        ;;

    cost)
        name=$1
        cost=$2
        if [[ $name ]]; then
            if ! git config remote.$name.url > /dev/null; then
                vdie "no such remote: $name"
            fi
            if [[ $cost == @(-|-u|--unset) ]]; then
                git config --unset remote.$name.annex-cost
            elif [[ $cost ]]; then
                git config remote.$name.annex-cost "$cost"
            else
                git config remote.$name.annex-cost
            fi
        else
            annex remotes
        fi
        ;;

    # Simple overrides chaining to the 'default' case
    add)
        for arg; do
            if [[ $arg == *.part ]]; then
                err "will not add .part file: $arg"
            elif [[ -e $arg && -f $arg && ! -L $arg && ! -s $arg ]]; then
                shopt -s nullglob
                parts=("$arg".*.part)
                shopt -u nullglob
                if (( ${#parts[@]} )); then
                    err "file is still being downloaded: $arg -> $parts"
                else
                    err "will not add 0-byte file: $arg"
                fi
            fi
        done
        (( !errors )) || exit
        ;;&
    addurl)
        args=()
        for arg; do
            arg=$(printf '%s\n' "$arg" | sed -E '
                s,https://(sourceforge\.net/project)s/([^/]+)/files/,https://downloads.\1/\2/,
                s,(https://downloads\.sourceforge\.net/.*)/download$,\1,
            ')
            args+=("$arg")
        done
        set -- "${args[@]}"
        if (( $# == 1 )); then
            set -- --file="$(urlencode.pl -dS "${1##*/}")" "$1"
        fi
        ;;&
    get)
        set -- "${@%@}"
        ;;&
    info)
        if (( ! $# )); then
            set -- --fast
        elif [[ $* == @(-v|--slow) ]]; then
            set --
        fi
        ;;&
    ls)
        cmd=find
        ;;&
    st)
        cmd=status
        ;;&
    sync)
        set -- --no-content "$@"
        ;;&
    *)
        if [[ ! $cmd ]]; then
            die "Usage: annex <command> ..."
        elif have annex-$cmd; then
            lib:is_nested -= 1
            annex-$cmd "$@"
        else
            git annex "$cmd" "$@"
        fi
        ;;
esac

exit $(( $? ? $? : errors ))
