【问题标题】:Rewrite history git filter-branch create / split into submodules / subprojects重写历史 git filter-branch 创建/拆分成子模块/子项目
【发布时间】:2012-11-16 22:17:59
【问题描述】:

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

假设我有这样的结构:

file1
file2
file3
dir1
dir2
library

现在我想重写历史记录,以便目录library 始终是一个 git 子模块。说,把指定的目录拆分成自己的子模块/子项目

这是我目前的代码:

文件rewrite-submodule(被调用)

cd project
git filter-branch --tree-filter $PWD/../$0-tree-filter --tag-name-filter cat -- --all

文件重写子模块树过滤器

#!/bin/bash 函数 gitCommit() { 取消设置 GIT_DIR 取消设置 GIT_WORK_TREE git添加-A if [ -n "$(git diff --cached --name-only)" ] 然后 # 要提交的东西 git commit -F $_msg 菲 } _git_dir=$GIT_DIR _git_work_tree=$GIT_WORK_TREE 取消设置 GIT_DIR 取消设置 GIT_WORK_TREE _dir=$密码 如果 [ -d "库" ] 然后 _msg=$(临时文件) git 日志 ${GIT_COMMIT}^! --format="%B" > $_msg git rm -r --cached 库 光盘库 如果 [ -d ".git" ] 然后 gitCommit 别的 混帐初始化 gitCommit 菲 光盘.. 导出 GIT_DIR=$_git_dir 导出 GIT_WORK_TREE=$_git_work_tree git 子模块添加 -f ./lib 菲 GIT_DIR=$_git_dir GIT_WORK_TREE=$_git_work_tree

此代码在主存储库中创建 .gitmodules 文件,而不是子模块提交条目(Subproject commit <sha1-hash> 行,由git diff 输出),并且目录library 中的文件仍然在主存储库中进行版本控制,并且不在子项目存储库中。

提前感谢任何提示

.gitmodules 看起来像这样:

[子模块“库”] 路径 = 库 网址 = ./库

【问题讨论】:

  • 我有一个模糊的概念,但你的问题到底是什么?
  • 我想改写历史,让目录library永远是git submodule
  • 在导入 CVS 时,我经常使用另一种策略。 以下不是现有git 存储库的解决方案:创建一个虚拟CVSROOT,其中CVS 文件已拆分为单独的CVS“模块”(也称为CVSROOT 下方的子目录)。然后使用git cvsimport 将它们分别导入到不同的git 存储库中。如何设置这样的“虚拟 CVSROOT”请参阅 permalink.de/tino/cvsimport(是的,这是一个很晚的评论)
  • 我将其作为stackoverflow.com/questions/12514197/… 的副本关闭了它,并且在重新打开以更正目标后,我无法再次投票关闭它。叹息。

标签: git rewrite git-submodules git-filter-branch subproject


【解决方案1】:

我解决了我自己的问题,这里是解决方案:

git-submodule-split library another_library

脚本git-submodule-split:

#!/bin/bash 设置-eu 如果 [ $# -eq 0 ] 然后 echo "用法:$0 子模块拆分" 菲 导出 _tmp=$(mktemp -d) 导出 _libs="$@" 对于我在 $_libs 做 mkdir -p $_tmp/$i 完毕 git filter-branch --commit-filter ' 函数 gitCommit() { git添加-A if [ -n "$(git diff --cached --name-only)" ] 然后 git commit -F $_msg 菲 } >/dev/null # 来自 git-filter-branch git checkout-index -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_DIR 取消设置 GIT_WORK_TREE 取消设置 GIT_INDEX_FILE _msg=$(临时文件) 猫 /dev/stdin > $_msg 对于我在 $_libs 做 如果 [ -d "$i" ] 然后 取消设置 GIT_DIR 取消设置 GIT_WORK_TREE 取消设置 GIT_INDEX_FILE 光盘$i 如果 [ -d ".git" ] 然后 gitCommit 别的 git init >/dev/null gitCommit 菲 光盘.. rsync -a -rtu $i/.git/ $_tmp/$i/.git/ 导出 GIT_DIR=$_git_dir 导出 GIT_WORK_TREE=$_git_work_tree 导出 GIT_INDEX_FILE=$_git_index_file git rm -q -r --cached $i git 子模块添加 ./$i >/dev/null 混帐添加 $i 菲 完毕 rm $_msg 导出 GIT_DIR=$_git_dir 导出 GIT_WORK_TREE=$_git_work_tree 导出 GIT_INDEX_FILE=$_git_index_file 如果 [ -f ".gitmodules" ] 然后 混帐添加 .gitmodules 菲 _new_rev=$(git write-tree) 转移 git commit-tree "$_new_rev" "$@"; ' --tag-name-filter cat -- --all 对于我在 $_libs 做 如果 [ -d "$_tmp/$i/.git" ] 然后 rsync -a -i -rtu $_tmp/$i/.git/ $i/.git/ 光盘$i git reset --hard 光盘.. 菲 完毕 rm -r $_tmp git for-each-ref refs/original --format="%(refname)" |读我的时候;做 git update-ref -d $i;完毕 git reflog expire --expire=now --all git gc --aggressive --prune=now

【讨论】:

    【解决方案2】:

    我有一个带有 utils 库的项目,该库已开始在其他项目中有用,并且希望将其历史记录拆分为子模块。没想到先看 SO,所以我自己写了,它在本地构建历史记录,所以速度要快一些,之后如果你愿意,可以设置辅助命令的 .gitmodules 文件等,然后推送子模块随时随地记录历史。

    剥离的命令本身在这里,文档在 cmets 中,在后面的未剥离的命令中。将其作为自己的命令运行,并设置subdir,例如subdir=utils git split-submodule,如果您要拆分utils 目录。这很 hacky,因为它是一次性的,但我在 Git 历史记录的 Documentation 子目录中对其进行了测试。

    #!/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}
    

    【讨论】:

    • 您的脚本在我的存储库中运行没有问题。但是,对于如何使用结果,我有点卡住了。我设法使用您建议的命令“git clone -sb ...”将子目录作为新的仓库拉出。它的新历史看起来不错。但是,我不太确定如何将原始存储库与此链接作为新的子模块。脚本运行后,工作目录现在只包含一个空的“utils”目录。 (它甚至在硬重置后恢复为空目录。)如果我运行“git submodule add ../utils”,我会收到错误“'utils'已经存在于索引中”。
    • 不确定这是否是正确的方法,但发出“git rm --cached utils”有助于我添加子模块。
    • @ARF 在我在本地 repo 中的使用设置中,使用了上面给出的 makefile 片段,我不太在意 git submodule 这样的辅助命令,它主要是为了帮助克隆人找到应该在该目录中检查的历史记录,但它已经在本地存储库中,那么克隆一个单独的存储库有什么好处?
    • 我想出了将新 repo 链接为子模块的过程。我是否应该在您的答案底部进行编辑以供您查看,详细说明所需的步骤?
    • @ARF 我使用的是在上面的文档中,配置/克隆/吸收/签出序列,因为我只是在主仓库中携带子模块历史记录,所以没有必要从外部获取副本.
    【解决方案3】:

    注意:子模块条目仅在您这样做时从父 repo 中创建

    git submodule init
    git submodule update
    

    您不需要在 rewrite-submodule-tree-filter 脚本中使用这些命令,因为它只是为了正确设置 .gitmodules 文件内容。

    只有在第一次使用父存储库时才会执行那些“git submodule”命令:请参阅“Cloning a Project with Submodules”。

    【讨论】:

    • 您好 VonC,我的意思是子项目输入行 Subproject commit <sha1-hash>git diff 输出。 library 目录中的文件仍然在主存储库中进行版本控制。
    • @MartinF 好的。在您的过滤器分支之后,您的 .gitmodules 文件是什么样的?
    • 我添加了 .gitmodules 文件的内容。
    • 所以我想如果你尝试使用git submodule initgit submodule update,那是行不通的,那么?
    • 我下午试试。
    【解决方案4】:

    这是适用于我在 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
    

    【讨论】:

      猜你喜欢
      • 2018-10-10
      • 1970-01-01
      • 2011-03-05
      • 1970-01-01
      • 1970-01-01
      • 2014-06-06
      • 2011-05-27
      • 1970-01-01
      • 2020-05-28
      相关资源
      最近更新 更多