如何移动化石库子目录树(到同一库中的其他地方,保留树级别)

How to move fossil repository subdirectory tree (to elsewhere within same repository, retaining tree levels)

我在化石结帐内有一个包含多级子目录的目录,我想移动到不同子目录中的另一个位置并保留多级目录结构。

例如,要将 a1 移动到下面的 a2 中,从 having(像手写的缩写 find 命令输出)开始:

a1/
a1/b/
a1/b/files
a1/c/
a1/c/d/
a1/c/d/more-files
a2/

我希望 fossil mv --hard a1 a2 结果:

a2/a1/
a2/a1/b/
a2/a1/b/files
a2/a1/c/
a2/a1/c/d/
a2/a1/c/d/more-files

就像普通的 unix mv 命令会产生的结果一样。理想情况下,保留 mv 的历史记录,以便它可以合并到另一个分支,并且对文件和更多文件的任何更改都完好无损;因为我可以直接删除文件然后将它们重新添加为新文件,但这是一个比我想要的更丑陋的解决方案。

fossil mv 命令(在 Linux 上的 v1.33 中)失去了多个级别,我最终将较低级别子目录中的所有文件移动到新位置的顶级目录中。

一个解决方案是编写一个脚本来单独移动每个目录,一次移动一个级别,因此它保留了结构。我想向化石开发人员推荐此功能。我可能会 post 脚本(下面,下面包含另一个依赖脚本)到我的 github (用户 jgbreezer),但现在,这个脚本(我称之为 fossilmvtree)。它会忽略结帐中不在化石中的文件,并会在有任何文件的地方留下旧的 files/dirs(我不相信它会删除它们):

#!/bin/bash
# =source tree
# =dest. dir
# supports fossil mv options
# moves single source tree as-is to under/new dest.dir (not reducing dir levels to flat structure under dest dir)
exclude=''

usage () {
    cat >&2 <<EOF
Usage: fossilmvtree [-x|--exclude= exclude_dirname] source dest"
-x option may be specified multiple times; do not specify full paths, just last
(filename/aka basename) of a directory to exclude from the move.
Command-line arguments are always included.
EOF
}

while [ -z "${1##-*}" ]
do
    case "" in
    -x|--exclude|--exclude=*)
        if [[ "${1#--exclude=}" == "" ]]
        then
            # separate arg, '--exclude=' not used
            shift
            arg=""
        else
            arg="${1#--exclude=}"
        fi
        excinfo="$excinfo $arg"
        # pruning is efficient
        exclude="$exclude -type d -name '${arg//\'/\\'}' -prune -o"
        ;;
    --case-sensitive)
        fossilopts="$fossilopts  "; shift;;
    -*)
        fossilopts="$fossilopts ";;
    esac
    shift
done
echo "excluding paths: $excinfo"
echo "fossil mv options: $fossilopts"

[ $# -eq 2 ] || { usage; exit 1; }
mv="$(which fossilmvrev 2>/dev/null)" || { usage; echo "error:Missing fossilmvrev" >&2; exit 1; }
src=""
srcdir="$(basename "$src")"
dst=""
if [ -f "$dst" ]
then
    # move src to new subdir of dst; otherwise we're renaming and moving
    [ -d "$dst" ] || { echo "error:Destination '$dst' exists but is not a directory" >&2; exit 1; }
    dst="$dst/$srcdir"
fi
#could set safe PATH (-execdir is cautious of relative/empty paths in $PATH but fossil binary might not be in std.location): PATH=/bin:/usr/bin:/usr/local/bin
eval find "$src" $exclude -type d -printf '%P\n' | { 
    while read -r dir
    do
        [ -z "$dir" ] || [[ "$src/$dir" == "$dst/$dir" ]] && continue
        echo
        echo "fossil mv $src/$dir/* $dst/$dir/"
        mkdir -p "$dst/$dir" || exit 1
        find "$src/$dir" -maxdepth 1 \! -type d -exec "$mv" $fossilopts "$dst/$dir" '{}' +
        rmdir "$src/$dir" # tidy up, as we only moved the files above (fossil doesn't really manage dirs)
        # if rmdir fails due to remaining files, let user manage that (rmdir will complain to stderr if so) -
        # likely to be unversioned files they might have forgotten about, shouldn't delete without user's knowledge.
    done
}

它只在我特定的化石检查中真正测试过一两次,尽管它已准备好成为一个可重复使用的脚本;请检查差异(建议在其他地方做一个干净的结账,然后 运行 它,然后使用 "diff -qr" 或其他东西与你的常规差异进行差异,然后再承诺检查它本身的行为)。

小心使用 -x/exclude 选项,我不确定它是否正常工作。

这取决于 fossilmvrev 脚本:

#!/bin/sh
# switch order of move arguments around to work with find -exec ... +
opts=''
while [ -z "${1##-*}" ]
do
    case "" in
    --case-sensitive) opts="$opts  "; shift 2;;
    *) opts="$opts "; shift;;
    esac
done
destdir=""
shift
fossil mv $opts "$@" $destdir

我认为有一个更简单的解决方案 (这在 Windows 下,但在 Linux 下类似)

md a2\a1
fossil mv a1 a2/a1 --hard