如何取消子模块Git子模块?

对Git子模块进行非子模块化,将所有代码带回核心存储库的最佳实践是什么?

125954 次浏览
  1. git rm --cached the_submodule_path
  2. .gitmodules文件中删除子模块部分,或者如果它是唯一的子模块,则删除该文件。
  3. 执行一个提交"removed submodule xyz"
  4. git add the_submodule_path
  5. 另一次提交“添加xyz的代码库”

我还没找到更简单的方法。你可以压缩3-5到一个步骤通过git commit -a - matter of taste。

如果你只是想把你的子模块代码放到主库中,你只需要删除子模块,并将文件重新添加到主库中:

git rm --cached submodule_path # delete reference to submodule HEAD (no trailing slash)
git rm .gitmodules             # if you have more than one submodules,
# you need to edit this file instead of deleting!
rm -rf submodule_path/.git     # make sure you have backup!!
git add submodule_path         # will add files instead of commit reference
git commit -m "remove submodule"

如果还想保留子模块的历史,可以使用一个小技巧:将子模块“合并”到主存储库中,这样结果将与以前相同,只是子模块文件现在位于主存储库中。

在主模块中,你需要做以下工作:

# Fetch the submodule commits into the main repository
git remote add submodule_origin git://url/to/submodule/origin
git fetch submodule_origin


# Start a fake merge (won't change any files, won't commit anything)
git merge -s ours --no-commit submodule_origin/master


# Do the same as in the first solution
git rm --cached submodule_path # delete reference to submodule HEAD
git rm .gitmodules             # if you have more than one submodules,
# you need to edit this file instead of deleting!
rm -rf submodule_path/.git     # make sure you have backup!!
git add submodule_path         # will add files instead of commit reference


# Commit and cleanup
git commit -m "removed submodule"
git remote rm submodule_origin

生成的存储库看起来有点奇怪:将会有多个初始提交。但是它不会给Git带来任何问题。

第二种解决方案的一大优点是,您仍然可以对最初在子模块中的文件运行git blamegit log。实际上,这里所发生的只是在一个存储库中重命名了许多文件,Git应该自动检测到这一点。如果你仍然有git log的问题,尝试一些选项(例如,--follow-M-C),可以更好地重命名和复制检测。

对我们来说,我们为两个项目创建了两个存储库,它们是如此耦合,以至于将它们分开没有任何意义,所以我们合并了它们。

我将首先展示如何合并每个主分支,然后解释如何将其扩展到你得到的每个分支,希望它能帮助到你。

如果你让子模块工作,你想把它转换成一个目录,你可以这样做:

git clone project_uri project_name

这里我们做了一个干净的克隆来工作。对于这个过程,您不需要初始化或更新子模块,所以跳过它。

cd project_name
vim .gitmodules

使用您喜欢的编辑器(或Vim)编辑.gitmodules以删除您计划替换的子模块。你需要删除的线条应该看起来像这样:

[submodule "lib/asi-http-request"]
path = lib/asi-http-request
url = https://github.com/pokeb/asi-http-request.git

保存文件后,

git rm --cached directory_of_submodule
git commit -am "Removed submodule_name as submodule"
rm -rf directory_of_submodule

在这里,我们完全删除了子模块关系,这样我们就可以就地创建将另一个repo带入项目。

git remote add -f submodule_origin submodule_uri
git fetch submodel_origin/master

这里我们获取要合并的子模块存储库。

git merge -s ours --no-commit submodule_origin/master

这里我们开始两个存储库的合并操作,但在提交之前停止。

git read-tree --prefix=directory_of_submodule/ -u submodule_origin/master

在这里,我们将子模块中的master内容发送到添加目录名前缀之前的目录

git commit -am "submodule_name is now part of main project"

在这里,我们完成了在合并中提交更改的过程。

完成此操作后,您可以推送,并重新开始使用任何其他分支来合并,只需签出存储库中将接收更改的分支,并更改您在合并和读取树操作中引入的分支。

git rm [-r] --cached submodule_path

返回

fatal: pathspec 'emr/normalizers/' did not match any files

上下文:我在我的子模块文件夹中执行了rm -r .git*,然后才意识到它们需要在我刚刚添加它们的主项目中去子模块化。我得到了上面的错误去子模化一些,但不是所有的。不管怎样,我通过运行,(当然,在rm -r .git*之后)来修复它们

mv submodule_path submodule_path.temp
git add -A .
git commit -m "De-submodulization phase 1/2"
mv submodule_path.temp submodule_path
git add -A .
git commit -m "De-submodulization phase 2/2"

注意,这并不能保存历史。

我发现它更方便(也?)从子模块获取本地提交数据,否则我会丢失它们。(无法推送,因为我没有遥控器)。所以我添加了submodule/。Git为remote_origin2,从该分支中提交并合并。 不确定我是否仍然需要远程子模块作为起源,因为我对git还不够熟悉

由于git 1.8.5(2013年11月) (不保留子模块的历史记录):

mv yoursubmodule yoursubmodule_tmp
git submodule deinit yourSubmodule
git rm yourSubmodule
mv yoursubmodule_tmp yoursubmodule
git add yoursubmodule

将:

  • unregister和卸载(即删除的内容)子模块(deinit,因此是mv 第一个),
  • 为你清理.gitmodules (rm),
  • 并删除父repo (rm)索引中表示子模块SHA1的特殊的条目

一旦子模块的删除完成(deinitgit rm),你可以重命名文件夹为原来的名称,并将其作为常规文件夹添加到git repo中。

注意:如果子模块是由旧Git (<1.8),你可能需要删除子模块本身内嵌套的.git文件夹,如评论 by 西蒙东


如果你需要保留子模块的历史,请参阅jsears回答,它使用git filter-branch

下面是@gyim的回答的一个稍微改进的版本(IMHO)。他在主工作副本中做了很多危险的改变,我认为在独立的克隆上操作,然后在最后将它们合并在一起要容易得多。

在一个单独的目录中(使错误更容易清理并再次尝试)检出顶部回购和子回购。

git clone ../main_repo main.tmp
git clone ../main_repo/sub_repo sub.tmp

首先编辑subrepo以将所有文件移动到所需的子目录中

cd sub.tmp
mkdir sub_repo_path
git mv `ls | grep -v sub_repo_path` sub_repo_path/
git commit -m "Moved entire subrepo into sub_repo_path"

注意头部

SUBREPO_HEAD=`git reflog | awk '{ print $1; exit; }'`

现在从主回购中删除子回购

cd ../main.tmp
rmdir sub_repo_path
vi .gitmodules  # remove config for submodule
git add -A
git commit -m "Removed submodule sub_repo_path in preparation for merge"

最后,合并它们

git fetch ../sub.tmp
# remove --allow-unrelated-histories if using git older than 2.9.0
git merge --allow-unrelated-histories $SUBREPO_HEAD

并完成了!安全而且没有任何魔法。

我找到的最佳答案是:

http://x3ro.de/2013/09/01/Integrating-a-submodule-into-the-parent-repository.html

这篇文章很好地解释了这个过程。

这里有很多答案,但它们似乎都过于复杂,可能不能达到你想要的效果。我相信大多数人都想保留他们的历史。

对于本例,主repo将是git@site.com:main/main.git,子模块repo将是git@site.com:main/child.git。这假设子模块位于父repo的根目录中。根据需要调整说明书。

首先克隆父repo并删除旧的子模块。

git clone git@site.com:main/main.git
git submodule deinit child
git rm child
git add --all
git commit -m "remove child submodule"

现在我们将把子回购添加到主回购的上游。

git remote add upstream git@site.com:main/child.git
git fetch upstream
git checkout -b merge-prep upstream/master

下一步假设您希望将merge-prep分支上的文件移动到与上面的子模块相同的位置,尽管您可以通过更改文件路径轻松更改位置。

mkdir child

将除.git文件夹外的所有文件夹和文件移动到子文件夹中。

git add --all
git commit -m "merge prep"

现在您可以简单地将文件合并回主分支。

git checkout master
git merge merge-prep # --allow-unrelated-histories merge-prep flag may be required

在运行git push之前,环顾四周,确保一切看起来都很好

你现在必须记住的一件事是,git日志默认不跟随移动的文件,但是通过运行git log --follow filename,你可以看到你文件的完整历史。

基于VonC的回答,我创建了一个简单的bash脚本。最后的add必须使用通配符,否则它将撤销子模块本身之前的rm。重要的是要添加子模块目录的内容,而不是在add命令中命名目录本身。

在一个名为git-integrate-submodule的文件中:

#!/usr/bin/env bash
mv "$1" "${1}_"
git submodule deinit "$1"
git rm "$1"
mv "${1}_" "$1"
git add "$1/**"

我已经创建了一个脚本,将子模块转换到一个简单的目录,同时保留所有的文件历史。它不会遭受其他解决方案所遭受的git log --follow <file>问题。它也是一个非常简单的单行调用,可以为您完成所有工作。G 'luck。

它建立在Lucas Jenß的出色工作之上,在他的博客文章"将子模块集成到父存储库中"中有描述,但是自动化了整个过程并清理了其他一些角落的情况。

最新的代码将在github上https://github.com/jeremysears/scripts/blob/master/bin/git-submodule-rewrite上进行错误修复,但为了适当的stackoverflow应答协议,我在下面包含了完整的解决方案。

用法:

$ git-submodule-rewrite <submodule-name>

git-submodule-rewrite:

#!/usr/bin/env bash


# This script builds on the excellent work by Lucas Jenß, described in his blog
# post "Integrating a submodule into the parent repository", but automates the
# entire process and cleans up a few other corner cases.
# https://x3ro.de/2013/09/01/Integrating-a-submodule-into-the-parent-repository.html


function usage() {
echo "Merge a submodule into a repo, retaining file history."
echo "Usage: $0 <submodule-name>"
echo ""
echo "options:"
echo "  -h, --help                Print this message"
echo "  -v, --verbose             Display verbose output"
}


function abort {
echo "$(tput setaf 1)$1$(tput sgr0)"
exit 1
}


function request_confirmation {
read -p "$(tput setaf 4)$1 (y/n) $(tput sgr0)"
[ "$REPLY" == "y" ] || abort "Aborted!"
}


function warn() {
cat << EOF
This script will convert your "${sub}" git submodule into
a simple subdirectory in the parent repository while retaining all
contents and file history.


The script will:
* delete the ${sub} submodule configuration from .gitmodules and
.git/config and commit it.
* rewrite the entire history of the ${sub} submodule so that all
paths are prefixed by ${path}.
This ensures that git log will correctly follow the original file
history.
* merge the submodule into its parent repository and commit it.


NOTE: This script might completely garble your repository, so PLEASE apply
this only to a fresh clone of the repository where it does not matter if
the repo is destroyed.  It would be wise to keep a backup clone of your
repository, so that you can reconstitute it if need be.  You have been
warned.  Use at your own risk.


EOF


request_confirmation "Do you want to proceed?"
}


function git_version_lte() {
OP_VERSION=$(printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' '\n' | head -n 4))
GIT_VERSION=$(git version)
GIT_VERSION=$(printf "%03d%03d%03d%03d" $(echo "${GIT_VERSION#git version}" | tr '.' '\n' | head -n 4))
echo -e "${GIT_VERSION}\n${OP_VERSION}" | sort | head -n1
[ ${OP_VERSION} -le ${GIT_VERSION} ]
}


function main() {


warn


if [ "${verbose}" == "true" ]; then
set -x
fi


# Remove submodule and commit
git config -f .gitmodules --remove-section "submodule.${sub}"
if git config -f .git/config --get "submodule.${sub}.url"; then
git config -f .git/config --remove-section "submodule.${sub}"
fi
rm -rf "${path}"
git add -A .
git commit -m "Remove submodule ${sub}"
rm -rf ".git/modules/${sub}"


# Rewrite submodule history
local tmpdir="$(mktemp -d -t submodule-rewrite-XXXXXX)"
git clone "${url}" "${tmpdir}"
pushd "${tmpdir}"
local tab="$(printf '\t')"
local filter="git ls-files -s | sed \"s/${tab}/${tab}${path}\//\" | GIT_INDEX_FILE=\${GIT_INDEX_FILE}.new git update-index --index-info && mv \${GIT_INDEX_FILE}.new \${GIT_INDEX_FILE}"
git filter-branch --index-filter "${filter}" HEAD
popd


# Merge in rewritten submodule history
git remote add "${sub}" "${tmpdir}"
git fetch "${sub}"


if git_version_lte 2.8.4
then
# Previous to git 2.9.0 the parameter would yield an error
ALLOW_UNRELATED_HISTORIES=""
else
# From git 2.9.0 this parameter is required
ALLOW_UNRELATED_HISTORIES="--allow-unrelated-histories"
fi


git merge -s ours --no-commit ${ALLOW_UNRELATED_HISTORIES} "${sub}/master"
rm -rf tmpdir


# Add submodule content
git clone "${url}" "${path}"
rm -rf "${path}/.git"
git add "${path}"
git commit -m "Merge submodule contents for ${sub}"
git config -f .git/config --remove-section "remote.${sub}"


set +x
echo "$(tput setaf 2)Submodule merge complete. Push changes after review.$(tput sgr0)"
}


set -euo pipefail


declare verbose=false
while [ $# -gt 0 ]; do
case "$1" in
(-h|--help)
usage
exit 0
;;
(-v|--verbose)
verbose=true
;;
(*)
break
;;
esac
shift
done


declare sub="${1:-}"


if [ -z "${sub}" ]; then
>&2 echo "Error: No submodule specified"
usage
exit 1
fi


shift


if [ -n "${1:-}" ]; then
>&2 echo "Error: Unknown option: ${1:-}"
usage
exit 1
fi


if ! [ -d ".git" ]; then
>&2 echo "Error: No git repository found.  Must be run from the root of a git repository"
usage
exit 1
fi


declare path="$(git config -f .gitmodules --get "submodule.${sub}.path")"
declare url="$(git config -f .gitmodules --get "submodule.${sub}.url")"


if [ -z "${path}" ]; then
>&2 echo "Error: Submodule not found: ${sub}"
usage
exit 1
fi


if ! [ -d "${path}" ]; then
>&2 echo "Error: Submodule path not found: ${path}"
usage
exit 1
fi


main

以下是我觉得最好的方法。简单的。

在子模块repo中,你想从HEAD合并到main repo:

  • git checkout -b "mergeMe"
  • mkdir "foo/bar/myLib/"(与你想要在主repo上的文件的路径相同)
  • git mv * "foo/bar/myLib/"(移动所有到路径)
  • git commit -m "ready to merge into main"

在删除子模块并清除路径"foo/bar/myLib"后,回到主repo:

  • git merge --allow-unrelated-histories SubmoduleOriginRemote/mergeMe

繁荣做

记录保存

不用担心


注意,这与其他一些答案几乎相同。但这假设你拥有子模块repo。此外,这也使子模块的未来上游更改变得容易。

在主回购

  • Git rm——缓存[submodules_repo]
  • git commit -m "子模块被删除;
  • Git push origin [master]

在子模块repo

  • Rm -rf .git

还是主回购

  • Git add [submodules_repo]
  • Git添加。
  • git commit -m " Submodules repo added to main."
  • Git push origin [master]