如何最佳实践地取消 Git 子模块并将所有代码合并回核心代码库?
如果您只想将子模块代码放入主存储库中,则只需删除子模块并将文件重新添加到主存储库中:
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 blame
或git log
。实际上,在这里发生的只是将许多文件重命名为一个存储库内的操作,Git应该会自动检测到这一点。如果您仍然遇到git log
的问题,请尝试使用一些选项(例如--follow
,-M
,-C
),这些选项可以更好地进行重命名和复制检测。git merge
确保每个文件都会有一个“先前的提交”(在合并的两个“侧”之一)。 - gyimmkdir foo && git mv !(foo) foo && git commit
。 - Chris Down--allow-unrelated-histories
以强制合并,因为会收到 fatal: refusing to merge unrelated histories
的错误提示。更多信息请参见此处:https://github.com/git/git/blob/master/Documentation/RelNotes/2.9.0.txt#L58-L68 - vaskortgit log --follow <file>
问题。此外,这是一个非常简单的一行调用,可以为您完成所有工作。祝你好运。$ 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
curl https://raw.githubusercontent.com/jeremysears/scripts/master/bin/git-submodule-rewrite > git-submodule-rewrite.sh
,并执行 ./git-submodule-rewrite.sh <submodule-name>
命令,确保所有工作都能在没有错误的情况下完成。 - Alexey自 git 1.8.5 (2013年11月) 起(不保留子模块的历史记录):
mv yoursubmodule yoursubmodule_tmp
git submodule deinit yourSubmodule
git rm yourSubmodule
mv yoursubmodule_tmp yoursubmodule
git add yoursubmodule
这将会:
deinit
,因此先使用mv
),.gitmodules
文件(使用rm
),rm
)。一旦子模块的移除完成(使用deinit
和git rm
),您可以将文件夹重命名回其原始名称,并将其作为常规文件夹添加到 git 仓库中。
注意: 如果子模块是由旧版本的Git (< 1.8)创建的,则可能需要删除子模块本身的嵌套.git
文件夹,如 commented by Simon East 所述。
git filter-branch
。git submodule deinit
)确实清除了它,但是它没有从索引中删除特殊条目,对吗? - VonCgit rm --cached the_submodule_path
:从Git中删除子模块。.gitmodules
文件中删除子模块部分,或者如果它是唯一的子模块,则删除该文件。git add the_submodule_path
:将更改后的代码库重新添加到Git。目前我还没有找到更简单的方法。您可以通过使用git commit -a
将3-5步骤压缩为一个步骤,这取决于个人喜好。
.gitmodules
而不是.submodules
吗? - imz -- Ivan Zakharyaschev.gitmodules
而不是 .submodules
。 - Dr. Housegit add
之前,我必须先删除子模块中的.git
目录。 - Carsongit@site.com:main/main.git
,子模块仓库是git@site.com:main/child.git
。这假定子模块位于父仓库的根目录中。根据需要调整说明。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 log
不能跟踪移动的文件,但是通过运行git log --follow filename
,你可以查看文件的完整历史记录。
git merge merge-prep
,但是收到了错误信息 fatal: refusing to merge unrelated histories
。解决方法是:git merge --allow-unrelated-histories merge-prep
。 - humblehacker我们曾经为两个项目创建了两个仓库,但这些项目是如此耦合,以至于将它们分开没有任何意义,所以我们将它们合并了。
我将展示如何首先合并每个仓库的主分支,然后解释如何扩展到您拥有的每个分支,希望对您有所帮助。
如果您的子模块可以工作,并且您想将其转换为当前目录,可以执行以下操作:
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
在这里,我们完全移除子模块关系,以便我们可以将其他存储库就地添加到项目中。
git remote add -f submodule_origin submodule_uri
git fetch submodel_origin/master
在这里,我们获取子模块存储库以进行合并。git merge -s ours --no-commit submodule_origin/master
我们在这里开始了2个代码库的合并操作,但在提交之前停止。
git read-tree --prefix=directory_of_submodule/ -u submodule_origin/master
在这里,我们将子模块中的主分支内容发送到其之前的目录,然后再添加一个目录名称。
git commit -am "submodule_name is now part of main project"
在这里,我们完成合并所做的更改提交程序。
完成此操作后,您可以推送并开始与任何其他要合并的分支一起工作,只需检出您的存储库中将接收更改的分支,并更改您正在合并和读取树操作中带入的分支。
git log original_path_of_file_in_submodule
来获取合并子模块文件的历史记录,即在 git 存储库中为该文件注册的路径(尽管该文件不再存在于文件系统中),即使子模块文件现在位于 submodule_path/new_path_of_file
。 - Anentropicgit log --follow $file_from_read_tree
或者使用其他选项都不会起作用。最终我做的与此相同,唯一的例外是在子模块中有一个本地分支,将其所有文件重命名为新的“子树路径”,执行“ours”合并,然后使用空前缀读取树并合并。这样可以使 git log --follow
找到提交记录。您需要暂时删除/添加任何嵌套的子模块。真是麻烦! - joonas这是对 @gyim 回答的稍微改进了一下(在我看来)。他在主要的工作副本中进行了一些危险的更改,我认为在单独的克隆上操作,然后在最后合并它们在一起会更容易。
在一个单独的目录中(使错误更容易清理和重试),检出顶级存储库和子存储库。
git clone ../main_repo main.tmp
git clone ../main_repo/sub_repo sub.tmp
首先编辑子仓库,将所有文件移动到所需的子目录中。
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"
注意HEAD标签
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
完成了!安全地且没有任何魔法。
subrepo
的目录并且其中有内容,这个代码还能正常工作吗? - detlygit merge $SUBREPO_HEAD fatal: refusing to merge unrelated histories
在这种情况下,我应该使用
git merge $SUBREPO_HEAD --allow-unrelated-histories
吗?还是说它可以正常工作,而我犯了一个错误? - Ti-mhttp://x3ro.de/2013/09/01/Integrating-a-submodule-into-the-parent-repository.html
这篇文章讲解得非常清晰。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 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"
git submodule deinit
命令,请参见我的回答。 - VonCgit submodule deinit asubmodule; git rm asubmodule
命令即可,如下面我的回答所示。 - VonC