重写历史git筛选器分支创建/拆分为子模块/子项目

重写历史git筛选器分支创建/拆分为子模块/子项目,git,rewrite,git-submodules,git-filter-branch,subproject,Git,Rewrite,Git Submodules,Git Filter Branch,Subproject,我目前正在将cvs项目导入git。 导入后,我想重写历史记录,将现有目录移动到单独的子模块中 假设我有一个这样的结构: 文件1 文件2 文件3 dir1 dir2 图书馆 现在我想重写历史记录,以便目录库始终是git子模块。例如,将指定的目录拆分为它们自己的子模块/子项目 这是我当前的代码: 文件重写子模块(称为) 文件重写子模块树过滤器 注意:子模块条目仅在创建时从父回购a创建 git submodule init git submodule update 您不需要在重写子模块树过滤器脚

我目前正在将cvs项目导入git。
导入后,我想重写历史记录,将现有目录移动到单独的子模块中

假设我有一个这样的结构:


文件1
文件2
文件3
dir1
dir2
图书馆

现在我想重写历史记录,以便目录
始终是git子模块。例如,将指定的目录拆分为它们自己的子模块/子项目

这是我当前的代码:

文件重写子模块(称为)

文件重写子模块树过滤器


注意:子模块条目仅在创建时从父回购a创建

git submodule init
git submodule update
您不需要在
重写子模块树过滤器
脚本中使用这些命令,因为它只是关于正确设置
.gitmodules
文件内容


只有在第一次使用父repo时,才能执行那些“
git submodule
”命令:请参见”。

我解决了自己的问题,下面是解决方案:

git子模块拆分库另一个\u库

脚本
git子模块拆分

#!/bin/bash set -eu if [ $# -eq 0 ] then echo "Usage: $0 submodules-to-split" fi export _tmp=$(mktemp -d) export _libs="$@" for i in $_libs do mkdir -p $_tmp/$i done git filter-branch --commit-filter ' function gitCommit() { git add -A if [ -n "$(git diff --cached --name-only)" ] then git commit -F $_msg fi } >/dev/null # from git-filter-branch git checkout-index -f -u -a || die "Could not checkout the index" # files that $commit removed are now still in the working tree; # remove them, else they would be added again git clean -d -q -f -x _git_dir=$GIT_DIR _git_work_tree=$GIT_WORK_TREE _git_index_file=$GIT_INDEX_FILE unset GIT_DIR unset GIT_WORK_TREE unset GIT_INDEX_FILE _msg=$(tempfile) cat /dev/stdin > $_msg for i in $_libs do if [ -d "$i" ] then unset GIT_DIR unset GIT_WORK_TREE unset GIT_INDEX_FILE cd $i if [ -d ".git" ] then gitCommit else git init >/dev/null gitCommit fi cd .. rsync -a -rtu $i/.git/ $_tmp/$i/.git/ export GIT_DIR=$_git_dir export GIT_WORK_TREE=$_git_work_tree export GIT_INDEX_FILE=$_git_index_file git rm -q -r --cached $i git submodule add ./$i >/dev/null git add $i fi done rm $_msg export GIT_DIR=$_git_dir export GIT_WORK_TREE=$_git_work_tree export GIT_INDEX_FILE=$_git_index_file if [ -f ".gitmodules" ] then git add .gitmodules fi _new_rev=$(git write-tree) shift git commit-tree "$_new_rev" "$@"; ' --tag-name-filter cat -- --all for i in $_libs do if [ -d "$_tmp/$i/.git" ] then rsync -a -i -rtu $_tmp/$i/.git/ $i/.git/ cd $i git reset --hard cd .. fi done rm -r $_tmp git for-each-ref refs/original --format="%(refname)" | while read i; do git update-ref -d $i; done git reflog expire --expire=now --all git gc --aggressive --prune=now #!/bin/bash set-eu 如果[$#-eq 0] 然后 echo“用法:$0要拆分的子模块” fi 导出_tmp=$(mktemp-d) 导出_libs=“$@” 以美元为单位的i 做 mkdir-p$\u tmp/$i 完成 git筛选器分支--提交筛选器' 函数gitCommit() { git添加-A if[-n“$(git diff--cached--name only)”] 然后 git提交-F$\u msg fi }>/dev/null #来自git过滤器分支 git签出索引-f-u-a | | die“无法签出索引” #$commit删除的文件现在仍在工作树中; #删除它们,否则会再次添加它们 git clean-d-q-f-x _git_dir=$git_dir _git_work_tree=$git_work_tree _git_index_file=$git_index_file 未设置GIT_目录 取消设置GIT\u工作树 取消设置GIT\u索引\u文件 _msg=$(临时文件) cat/dev/stdin>$\u msg 以美元为单位的i 做 如果[-d“$i”] 然后 未设置GIT_目录 取消设置GIT\u工作树 取消设置GIT\u索引\u文件 cd$i 如果[-d“.git”] 然后 gitCommit 其他的 git init>/dev/null gitCommit fi 光盘 rsync-a-rtu$i/.git/$\u tmp/$i/.git/ 导出GIT\u DIR=$\u GIT\u DIR 导出GIT\u工作树=$\u GIT\u工作树 导出GIT\u索引\u文件=$\u GIT\u索引\u文件 gitrm-q-r——缓存的$i git子模块add./$i>/dev/null 加币$i fi 完成 马来西亚元整 导出GIT\u DIR=$\u GIT\u DIR 导出GIT\u工作树=$\u GIT\u工作树 导出GIT\u索引\u文件=$\u GIT\u索引\u文件 如果[-f“.gitmodules”] 然后 git add.git模块 fi _新版本=$(git写入树) 转移 git提交树“$\u new\u rev”“$@”; '--标记名筛选器类别--all 以美元为单位的i 做 如果[-d“$\u tmp/$i/.git”] 然后 rsync-a-i-rtu$\u tmp/$i/.git/$i/.git/ cd$i git重置——硬 光盘 fi 完成 rm-r$\u tmp 每个ref/original的git——读取i时格式为=“%(refname)”;做git更新ref-d$i;完成 git reflog expire--expire=now--all git-gc--aggressive--prune=now
下面是一个在MacOSX上对我有用的更新答案。主要的变化是使用pushd/popd来更改目录,这样子模块就可以像module/glop一样,而不仅仅是glop

#!/bin/bash

set -eu

if [ $# -eq 0 ]
then
    echo "Usage: $0 submodules-to-split"
fi

export _tmp=$(mktemp -d /tmp/git-submodule-split.XXXXXX)
export _libs="$@"
for i in $_libs
do
    mkdir -p $_tmp/$i
done

git filter-branch --commit-filter '
function gitCommit()
{
    git add -A
    if [ -n "$(git diff --cached --name-only)" ]
    then
        git commit -F $_msg
    fi
} >/dev/null

# from git-filter-branch
git checkout-index -f -u -a || die "Could not checkout the index"
# files that $commit removed are now still in the working tree;
# remove them, else they would be added again
git clean -d -q -f -x >&2

_git_dir=$GIT_DIR
_git_work_tree=$GIT_WORK_TREE
_git_index_file=$GIT_INDEX_FILE
unset GIT_DIR
unset GIT_WORK_TREE
unset GIT_INDEX_FILE

_msg=$(mktemp /tmp/git-submodule-split-msg.XXXXXX)
cat /dev/stdin > $_msg
for i in $_libs
do
    if [ -d "$i" ]
    then
        unset GIT_DIR
        unset GIT_WORK_TREE
        unset GIT_INDEX_FILE
        pushd $i > /dev/null
        if [ -d ".git" ]
        then
            gitCommit
        else
            git init >/dev/null
            gitCommit
        fi
        popd > /dev/null
        mkdir -p $_tmp/$i
        rsync -a -rtu $i/.git/ $_tmp/$i/.git/
        export GIT_DIR=$_git_dir
        export GIT_WORK_TREE=$_git_work_tree
        export GIT_INDEX_FILE=$_git_index_file
        git rm -q -r --cached $i >&2
        git submodule add ./$i $i >&2
        git add $i >&2
    fi
done
export GIT_DIR=$_git_dir
export GIT_WORK_TREE=$_git_work_tree
export GIT_INDEX_FILE=$_git_index_file

if [ -f ".gitmodules" ]
then
    git add .gitmodules >&2
fi

_new_rev=$(git write-tree)
shift
git commit-tree -F $_msg "$_new_rev" $@;
rm -f $_msg
' --tag-name-filter cat -- --all

for i in $_libs
do
    if [ -d "$_tmp/$i/.git" ]
    then
        rsync -a -i -rtu $_tmp/$i/.git/ $i/.git/
        pushd $i
        git reset --hard
        popd
    fi
done
rm -rf $_tmp

git for-each-ref refs/original --format="%(refname)" | while read i; do git update-ref -d $i; done

git reflog expire --expire=now --all
git gc --aggressive --prune=now

我有一个带有
utils
库的项目,该库在其他项目中开始有用,并希望将其历史分割为子模块。没有想到要先查看,所以我编写了自己的,它在本地构建历史记录,因此速度要快一点,之后如果需要,您可以设置helper命令的
.gitmodules
文件等,并将子模块历史记录推到您想要的任何位置

剥离命令本身在这里,文档在注释中,在后面的非剥离命令中。将其作为自己的命令运行,并设置
subdir
set,如果要拆分
utils
目录,则类似于
subdir=utils git split submodule
。它之所以有黑客行为是因为它是一次性的,但我在Git历史记录中的文档子目录中对它进行了测试

#!/bin/bash
# put this or the commented version below in e.g. ~/bin/git-split-submodule
${GIT_COMMIT-exec git filter-branch --index-filter "subdir=$subdir; ${debug+debug=$debug;} $(sed 1,/SNIP/d "$0")" "$@"}
${debug+set -x}
fam=(`git rev-list --no-walk --parents $GIT_COMMIT`)
pathcheck=(`printf "%s:$subdir\\n" ${fam[@]} \
    | git cat-file --batch-check='%(objectname)' | uniq`)
[[ $pathcheck = *:* ]] || {
    subfam=($( set -- ${fam[@]}; shift;
        for par; do tpar=`map $par`; [[ $tpar != $par ]] &&
            git rev-parse -q --verify $tpar:"$subdir"
        done
    ))
    git rm -rq --cached --ignore-unmatch  "$subdir"
    if (( ${#pathcheck[@]} == 1 && ${#fam[@]} > 1 && ${#subfam[@]} > 0)); then
        git update-index --add --cacheinfo 160000,$subfam,"$subdir"
    else
        subnew=`git cat-file -p $GIT_COMMIT | sed 1,/^$/d \
            | git commit-tree $GIT_COMMIT:"$subdir" $(
                ${subfam:+printf ' -p %s' ${subfam[@]}}) 2>&-
            ` &&
        git update-index --add --cacheinfo 160000,$subnew,"$subdir"
    fi
}
${debug+set +x}


我有一个模糊的概念,但你的问题到底是什么?我想重写历史记录,以便目录库始终是CVS的
git子模块
,导入时我经常使用另一种策略。以下不是现有的
git
repos的解决方案:创建一个虚拟
CVSROOT
,其中
CVS
文件已拆分为单独的
CVS
“模块”(以下简称子目录
CVSROOT
)。然后使用
git cvsimport
将它们分别导入不同的
git
repos。如何设置这样一个“虚拟CVSROOT”见(是的,这是一个很晚的评论)我决定将其作为一个副本关闭,在重新打开以更正目标后,我不能投票再次关闭它。唉。您好,VonC,我的意思是,在子项目条目中,
subproject commit
output by
git diff
。目录库中的文件仍在主存储库中进行版本控制。@MartinF确定。在您的筛选器分支之后,
.gitmodules
文件是什么样子的?我添加了.gitmodules文件的内容。因此,我想如果您尝试
git子模块init
git子模块更新
,那将不起作用,那么我将在今天下午尝试。您的脚本在我的repo上运行时没有问题。然而,对于如何使用结果,我有点困惑。我使用您建议的命令“git clone-sb…”将子目录作为一个新的repo拉出。是东北 #!/bin/bash set -eu if [ $# -eq 0 ] then echo "Usage: $0 submodules-to-split" fi export _tmp=$(mktemp -d) export _libs="$@" for i in $_libs do mkdir -p $_tmp/$i done git filter-branch --commit-filter ' function gitCommit() { git add -A if [ -n "$(git diff --cached --name-only)" ] then git commit -F $_msg fi } >/dev/null # from git-filter-branch git checkout-index -f -u -a || die "Could not checkout the index" # files that $commit removed are now still in the working tree; # remove them, else they would be added again git clean -d -q -f -x _git_dir=$GIT_DIR _git_work_tree=$GIT_WORK_TREE _git_index_file=$GIT_INDEX_FILE unset GIT_DIR unset GIT_WORK_TREE unset GIT_INDEX_FILE _msg=$(tempfile) cat /dev/stdin > $_msg for i in $_libs do if [ -d "$i" ] then unset GIT_DIR unset GIT_WORK_TREE unset GIT_INDEX_FILE cd $i if [ -d ".git" ] then gitCommit else git init >/dev/null gitCommit fi cd .. rsync -a -rtu $i/.git/ $_tmp/$i/.git/ export GIT_DIR=$_git_dir export GIT_WORK_TREE=$_git_work_tree export GIT_INDEX_FILE=$_git_index_file git rm -q -r --cached $i git submodule add ./$i >/dev/null git add $i fi done rm $_msg export GIT_DIR=$_git_dir export GIT_WORK_TREE=$_git_work_tree export GIT_INDEX_FILE=$_git_index_file if [ -f ".gitmodules" ] then git add .gitmodules fi _new_rev=$(git write-tree) shift git commit-tree "$_new_rev" "$@"; ' --tag-name-filter cat -- --all for i in $_libs do if [ -d "$_tmp/$i/.git" ] then rsync -a -i -rtu $_tmp/$i/.git/ $i/.git/ cd $i git reset --hard cd .. fi done rm -r $_tmp git for-each-ref refs/original --format="%(refname)" | while read i; do git update-ref -d $i; done git reflog expire --expire=now --all git gc --aggressive --prune=now
#!/bin/bash

set -eu

if [ $# -eq 0 ]
then
    echo "Usage: $0 submodules-to-split"
fi

export _tmp=$(mktemp -d /tmp/git-submodule-split.XXXXXX)
export _libs="$@"
for i in $_libs
do
    mkdir -p $_tmp/$i
done

git filter-branch --commit-filter '
function gitCommit()
{
    git add -A
    if [ -n "$(git diff --cached --name-only)" ]
    then
        git commit -F $_msg
    fi
} >/dev/null

# from git-filter-branch
git checkout-index -f -u -a || die "Could not checkout the index"
# files that $commit removed are now still in the working tree;
# remove them, else they would be added again
git clean -d -q -f -x >&2

_git_dir=$GIT_DIR
_git_work_tree=$GIT_WORK_TREE
_git_index_file=$GIT_INDEX_FILE
unset GIT_DIR
unset GIT_WORK_TREE
unset GIT_INDEX_FILE

_msg=$(mktemp /tmp/git-submodule-split-msg.XXXXXX)
cat /dev/stdin > $_msg
for i in $_libs
do
    if [ -d "$i" ]
    then
        unset GIT_DIR
        unset GIT_WORK_TREE
        unset GIT_INDEX_FILE
        pushd $i > /dev/null
        if [ -d ".git" ]
        then
            gitCommit
        else
            git init >/dev/null
            gitCommit
        fi
        popd > /dev/null
        mkdir -p $_tmp/$i
        rsync -a -rtu $i/.git/ $_tmp/$i/.git/
        export GIT_DIR=$_git_dir
        export GIT_WORK_TREE=$_git_work_tree
        export GIT_INDEX_FILE=$_git_index_file
        git rm -q -r --cached $i >&2
        git submodule add ./$i $i >&2
        git add $i >&2
    fi
done
export GIT_DIR=$_git_dir
export GIT_WORK_TREE=$_git_work_tree
export GIT_INDEX_FILE=$_git_index_file

if [ -f ".gitmodules" ]
then
    git add .gitmodules >&2
fi

_new_rev=$(git write-tree)
shift
git commit-tree -F $_msg "$_new_rev" $@;
rm -f $_msg
' --tag-name-filter cat -- --all

for i in $_libs
do
    if [ -d "$_tmp/$i/.git" ]
    then
        rsync -a -i -rtu $_tmp/$i/.git/ $i/.git/
        pushd $i
        git reset --hard
        popd
    fi
done
rm -rf $_tmp

git for-each-ref refs/original --format="%(refname)" | while read i; do git update-ref -d $i; done

git reflog expire --expire=now --all
git gc --aggressive --prune=now
#!/bin/bash
# put this or the commented version below in e.g. ~/bin/git-split-submodule
${GIT_COMMIT-exec git filter-branch --index-filter "subdir=$subdir; ${debug+debug=$debug;} $(sed 1,/SNIP/d "$0")" "$@"}
${debug+set -x}
fam=(`git rev-list --no-walk --parents $GIT_COMMIT`)
pathcheck=(`printf "%s:$subdir\\n" ${fam[@]} \
    | git cat-file --batch-check='%(objectname)' | uniq`)
[[ $pathcheck = *:* ]] || {
    subfam=($( set -- ${fam[@]}; shift;
        for par; do tpar=`map $par`; [[ $tpar != $par ]] &&
            git rev-parse -q --verify $tpar:"$subdir"
        done
    ))
    git rm -rq --cached --ignore-unmatch  "$subdir"
    if (( ${#pathcheck[@]} == 1 && ${#fam[@]} > 1 && ${#subfam[@]} > 0)); then
        git update-index --add --cacheinfo 160000,$subfam,"$subdir"
    else
        subnew=`git cat-file -p $GIT_COMMIT | sed 1,/^$/d \
            | git commit-tree $GIT_COMMIT:"$subdir" $(
                ${subfam:+printf ' -p %s' ${subfam[@]}}) 2>&-
            ` &&
        git update-index --add --cacheinfo 160000,$subnew,"$subdir"
    fi
}
${debug+set +x}
#!/bin/bash
# Git filter-branch to split a subdirectory into a submodule history.

# In each commit, the subdirectory tree is replaced in the index with an
# appropriate submodule commit.
# * If the subdirectory tree has changed from any parent, or there are
#   no parents, a new submodule commit is made for the subdirectory (with
#   the current commit's message, which should presumably say something
#   about the change). The new submodule commit's parents are the
#   submodule commits in any rewrites of the current commit's parents.
# * Otherwise, the submodule commit is copied from a parent.

# Since the new history includes references to the new submodule
# history, the new submodule history isn't dangling, it's incorporated.
# Branches for any part of it can be made casually and pushed into any
# other repo as desired, so hooking up the `git submodule` helper
# command's conveniences is easy, e.g.
#     subdir=utils git split-submodule master
#     git branch utils $(git rev-parse master:utils)
#     git clone -sb utils . ../utilsrepo
# and you can then submodule add from there in other repos, but really,
# for small utility libraries and such, just fetching the submodule
# histories into your own repo is easiest. Setup on cloning a
# project using "incorporated" submodules like this is:
#   setup:  utils/.git
#
#   utils/.git:
#       @if _=`git rev-parse -q --verify utils`; then \
#           git config submodule.utils.active true \
#           && git config submodule.utils.url "`pwd -P`" \
#           && git clone -s . utils -nb utils \
#           && git submodule absorbgitdirs utils \
#           && git -C utils checkout $$(git rev-parse :utils); \
#       fi
# with `git config -f .gitmodules submodule.utils.path utils` and
# `git config -f .gitmodules submodule.utils.url ./`; cloners don't
# have to do anything but `make setup`, and `setup` should be a prereq
# on most things anyway.

# You can test that a commit and its rewrite put the same tree in the
# same place with this function:
# testit ()
# {
#     tree=($(git rev-parse `git rev-parse $1`: refs/original/refs/heads/$1));
#     echo $tree `test $tree != ${tree[1]} && echo ${tree[1]}`
# }
# so e.g. `testit make~95^2:t` will print the `t` tree there and if
# the `t` tree at ~95^2 from the original differs it'll print that too.

# To run it, say `subdir=path/to/it git split-submodule` with whatever
# filter-branch args you want.

# $GIT_COMMIT is set if we're already in filter-branch, if not, get there:
${GIT_COMMIT-exec git filter-branch --index-filter "subdir=$subdir; ${debug+debug=$debug;} $(sed 1,/SNIP/d "$0")" "$@"}

${debug+set -x}
fam=(`git rev-list --no-walk --parents $GIT_COMMIT`)
pathcheck=(`printf "%s:$subdir\\n" ${fam[@]} \
    | git cat-file --batch-check='%(objectname)' | uniq`)

[[ $pathcheck = *:* ]] || {
    subfam=($( set -- ${fam[@]}; shift;
        for par; do tpar=`map $par`; [[ $tpar != $par ]] &&
            git rev-parse -q --verify $tpar:"$subdir"
        done
    ))

    git rm -rq --cached --ignore-unmatch  "$subdir"
    if (( ${#pathcheck[@]} == 1 && ${#fam[@]} > 1 && ${#subfam[@]} > 0)); then
        # one id same for all entries, copy mapped mom's submod commit
        git update-index --add --cacheinfo 160000,$subfam,"$subdir"
    else
        # no mapped parents or something changed somewhere, make new
        # submod commit for current subdir content.  The new submod
        # commit has all mapped parents' submodule commits as parents:
        subnew=`git cat-file -p $GIT_COMMIT | sed 1,/^$/d \
            | git commit-tree $GIT_COMMIT:"$subdir" $(
                ${subfam:+printf ' -p %s' ${subfam[@]}}) 2>&-
            ` &&
        git update-index --add --cacheinfo 160000,$subnew,"$subdir"
    fi
}
${debug+set +x}