将 Git 文件夹追溯转换为子模块?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12514197/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Convert a Git folder to a submodule retrospectively?
提问by naught101
Quite often it is the case that you're writing a project of some kind, and after a while it becomes clear that some component of the project is actually useful as a standalone component (a library, perhaps). If you've had that idea from early on, then there's a fair chance that most of that code is in its own folder.
通常情况下,您正在编写某种类型的项目,一段时间后,很明显项目的某些组件实际上可以用作独立组件(也许是库)。如果您很早就有这个想法,那么很可能大部分代码都在它自己的文件夹中。
Is there a way to convert one of the sub directories in a Git project to a submodule?
有没有办法将 Git 项目中的子目录之一转换为子模块?
Ideally this would happen such that all of the code in that directory is removed from the parent project, and the submodule project is added in its place, with all the appropriate history, and such that all the parent project commits point to the correct submodule commits.
理想情况下,会发生这种情况,该目录中的所有代码都从父项目中删除,并且子模块项目添加到其位置,具有所有适当的历史记录,并且所有父项目提交都指向正确的子模块提交.
采纳答案by knittl
To isolate a subdirectory into its own repository, use filter-branch
on a clone of the original repository:
要将子目录隔离到其自己的存储库中,请filter-branch
在原始存储库的克隆上使用:
git clone <your_project> <your_submodule>
cd <your_submodule>
git filter-branch --subdirectory-filter 'path/to/your/submodule' --prune-empty -- --all
It's then nothing more than deleting your original directory and adding the submodule to your parent project.
那么它只不过是删除您的原始目录并将子模块添加到您的父项目中。
回答by zednight
First change dir to folder which will be a submodule. Then:
首先将目录更改为文件夹,这将是一个子模块。然后:
git init
git remote add origin repourl
git add .
git commit -am'first commit in submodule'
git push -u origin master
cd ..
rm -rf folder wich will be a submodule
git commit -am'deleting folder'
git submodule add repourl folder wich will be a submodule
git commit -am'adding submodule'
回答by oodavid
I know this is an old thread, but the answers here squash any related commits in other branches.
我知道这是一个旧线程,但这里的答案压缩了其他分支中的任何相关提交。
A simple way to clone and keep all those extra branches and commits:
一种克隆和保留所有这些额外分支和提交的简单方法:
1 - Make sure you have this git alias
1 - 确保你有这个 git 别名
git config --global alias.clone-branches '! git branch -a | sed -n "/\/HEAD /d; /\/master$/d; /remotes/p;" | xargs -L1 git checkout -t'
2 - Clone the remote, pull all branches, change the remote, filter your directory, push
2 - 克隆远程,拉所有分支,更改远程,过滤您的目录,推送
git clone [email protected]:user/existing-repo.git new-repo
cd new-repo
git clone-branches
git remote rm origin
git remote add origin [email protected]:user/new-repo.git
git remote -v
git filter-branch --subdirectory-filter my_directory/ -- --all
git push --all
git push --tags
回答by twalberg
It can be done, but it's not simple. If you search for git filter-branch
, subdirectory
and submodule
, there are some decent write-ups on the process. It essentially entails creating two clones of your project, using git filter-branch
to remove everything except the one subdirectory in one, and removing only that subdirectory in the other. Then you can establish the second repository as a submodule of the first.
可以做到,但并不简单。如果您搜索git filter-branch
,subdirectory
和submodule
,就会发现有关该过程的一些不错的文章。它本质上需要创建项目的两个克隆,git filter-branch
用于删除一个中除一个子目录之外的所有内容,并仅删除另一个中的该子目录。然后您可以建立第二个存储库作为第一个存储库的子模块。
回答by P. B.
Status quo
现状
Let's assume we have a repository called repo-old
which contains a subdirectorysub
that we would like to convert into a submodulewith its own repo repo-sub
.
假设我们有一个叫做库repo-old
包含一个子目录sub
,我们想转换成一个子模块有自己的回购repo-sub
。
It is further intended that the original repo repo-old
should be converted into a modified repo repo-new
where all commits touching the previously existing subdirectory sub
shall now point to the corresponding commits of our extracted submodule repo repo-sub
.
还打算将原始 reporepo-old
转换为修改后的 repo repo-new
,其中所有涉及先前存在的子目录sub
的提交现在都应指向我们提取的子模块 repo 的相应提交repo-sub
。
Let's change
让我们改变
It is possible to achieve this with the help of git filter-branch
in a two step process:
可以git filter-branch
在两步过程的帮助下实现这一点:
- Subdirectory extraction from
repo-old
torepo-sub
(already mentioned in the accepted answer) - Subdirectory replacement from
repo-old
torepo-new
(with proper commit mapping)
- 从
repo-old
到的子目录提取repo-sub
(已在接受的答案中提到) - 从
repo-old
到的子目录替换repo-new
(使用正确的提交映射)
Remark: I know that this question is old and it has already been mentioned that git filter-branch
is kind of deprecated and might be dangerous. But on the other hand it might help others with personal repositories that are easy to validate after conversion. So be warned! And please let me know if there is any other tool that does the same thing without being deprecated and is safe to use!
备注:我知道这个问题已经git filter-branch
过时了,并且已经提到它有点过时并且可能很危险。但另一方面,它可能会帮助其他人使用转换后易于验证的个人存储库。所以被警告!请让我知道是否有任何其他工具可以做同样的事情而不会被弃用并且可以安全使用!
I'll explain how I realized both steps on linux with git version 2.26.2 below. Older versions might work to some extend but that needs to be tested.
我将解释我如何在 linux 上使用 git 版本 2.26.2 下面实现这两个步骤。旧版本可能会在一定程度上起作用,但这需要进行测试。
For the sake of simplicity I will restrict myself to the case where there is just a master
branch and a origin
remote in the original repo repo-old
. Also be warned that I rely on temporary git tags with the prefix temp_
which are going to be removed in the process. So if there are already tags named similarily you might want to adjust the prefix below. And finally please be aware that I have not extensively tested this and there might be corner cases where the recipe fails. So please backup everything before proceeding!
为了简单起见,我将自己限制在原始 repo 中只有一个master
分支和一个origin
遥控器的情况下repo-old
。另请注意,我依赖于temp_
将在此过程中删除的带有前缀的临时 git 标签。因此,如果已经有类似命名的标签,您可能需要调整下面的前缀。最后请注意,我没有对此进行广泛的测试,并且可能存在配方失败的极端情况。所以请在继续之前备份所有内容!
The following bash snippets can be concatenated into one big script which should then be executed in the same folder where the repo repo-org
lives. It is not recommended to copy and paste everything directly into a command window (even though I have tested this successfully)!
以下 bash 片段可以连接成一个大脚本,然后应在存储库所在的同一文件夹中执行该脚本repo-org
。不建议将所有内容直接复制并粘贴到命令窗口中(即使我已成功测试)!
0. Preparation
0. 准备
Variables
变量
# Root directory where repo-org lives
# and a temporary location for git filter-branch
root="$PWD"
temp='/dev/shm/tmp'
# The old repository and the subdirectory we'd like to extract
repo_old="$root/repo-old"
repo_old_directory='sub'
# The new submodule repository, its url
# and a hash map folder which will be populated
# and later used in the filter script below
repo_sub="$root/repo-sub"
repo_sub_url='https://github.com/somewhere/repo-sub.git'
repo_sub_hashmap="$root/repo-sub.map"
# The new modified repository, its url
# and a filter script which is created as heredoc below
repo_new="$root/repo-new"
repo_new_url='https://github.com/somewhere/repo-new.git'
repo_new_filter="$root/repo-new.sh"
Filter script
过滤脚本
# The index filter script which converts our subdirectory into a submodule
cat << EOF > "$repo_new_filter"
#!/bin/bash
# Submodule hash map function
sub ()
{
local old_commit=$(git rev-list -1 $1 -- '$repo_old_directory')
if [ ! -z "$old_commit" ]
then
echo $(cat "$repo_sub_hashmap/$old_commit")
fi
}
# Submodule config
SUB_COMMIT=$(sub $GIT_COMMIT)
SUB_DIR='$repo_old_directory'
SUB_URL='$repo_sub_url'
# Submodule replacement
if [ ! -z "$SUB_COMMIT" ]
then
touch '.gitmodules'
git config --file='.gitmodules' "submodule.$SUB_DIR.path" "$SUB_DIR"
git config --file='.gitmodules' "submodule.$SUB_DIR.url" "$SUB_URL"
git config --file='.gitmodules' "submodule.$SUB_DIR.branch" 'master'
git add '.gitmodules'
git rm --cached -qrf "$SUB_DIR"
git update-index --add --cacheinfo 160000 $SUB_COMMIT "$SUB_DIR"
fi
EOF
chmod +x "$repo_new_filter"
1. Subdirectory extraction
1.子目录提取
cd "$root"
# Create a new clone for our new submodule repo
git clone "$repo_old" "$repo_sub"
# Enter the new submodule repo
cd "$repo_sub"
# Remove the old origin remote
git remote remove origin
# Loop over all commits and create temporary tags
for commit in $(git rev-list --all)
do
git tag "temp_$commit" $commit
done
# Extract the subdirectory and slice commits
mkdir -p "$temp"
git filter-branch --subdirectory-filter "$repo_old_directory" \
--tag-name-filter 'cat' \
--prune-empty --force -d "$temp" -- --all
# Populate hash map folder from our previously created tag names
mkdir -p "$repo_sub_hashmap"
for tag in $(git tag | grep "^temp_")
do
old_commit=${tag#'temp_'}
sub_commit=$(git rev-list -1 $tag)
echo $sub_commit > "$repo_sub_hashmap/$old_commit"
done
git tag | grep "^temp_" | xargs -d '\n' git tag -d 2>&1 > /dev/null
# Add the new url for this repository (and e.g. push)
git remote add origin "$repo_sub_url"
# git push -u origin master
2. Subdirectory replacement
2.子目录替换
cd "$root"
# Create a clone for our modified repo
git clone "$repo_old" "$repo_new"
# Enter the new modified repo
cd "$repo_new"
# Remove the old origin remote
git remote remove origin
# Replace the subdirectory and map all sliced submodule commits using
# the filter script from above
mkdir -p "$temp"
git filter-branch --index-filter "$repo_new_filter" \
--tag-name-filter 'cat' --force -d "$temp" -- --all
# Add the new url for this repository (and e.g. push)
git remote add origin "$repo_new_url"
# git push -u origin master
# Cleanup (commented for safety reasons)
# rm -rf "$repo_sub_hashmap"
# rm -f "$repo_new_filter"
Remark:If the newly created repo repo-new
hangs during git submodule update --init
then try to re-clone the repository recursively once instead:
备注:如果新创建的存储库repo-new
在此期间挂起,git submodule update --init
则尝试递归地重新克隆存储库一次:
cd "$root"
# Clone the new modified repo recursively
git clone --recursive "$repo_new" "$repo_new-tmp"
# Now use the newly cloned one
mv "$repo_new" "$repo_new-bak"
mv "$repo_new-tmp" "$repo_new"
# Cleanup (commented for safety reasons)
# rm -rf "$repo_new-bak"
回答by jthill
This does the conversion in-place, you can back it out as you would any filter-branch (I use git fetch . +refs/original/*:*
).
这会进行就地转换,您可以像处理任何过滤器分支一样将其退出(我使用git fetch . +refs/original/*:*
)。
I have a project with a utils
library that's started to be useful in other projects, and wanted to split its history off into a submodules. Didn't think to look on SO first so I wrote my own, it builds the history locally so it's a good bit faster, after which if you want you can set up the helper command's .gitmodules
file and such, and push the submodule histories themselves anywhere you want.
我有一个项目,该项目的utils
库开始在其他项目中有用,并希望将其历史拆分为子模块。没想到先看 SO 所以我自己写了,它在本地构建历史,所以速度要快一些,之后如果你愿意,你可以设置辅助命令的.gitmodules
文件等,并将子模块历史本身推送到任何地方你要。
The stripped command itself is here, the doc's in the comments, in the unstripped one that follows. Run it as its own command, with subdir
set, like subdir=utils git split-submodule
if you're splitting the utils
directory. It's hacky because it's a one-off, but I tested it on the Documentation subdirectory in the Git history.
剥离命令本身就在这里,文档在注释中,在随后的未剥离命令中。使用subdir
set作为它自己的命令运行它,就像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 "#!/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 `: refs/original/refs/heads/));
# 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 "##代码##")" "$@"}
${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}
")" "$@"}
${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}
##代码##